<commit_before>use std::collections::BTreeMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::channel;
use std::sync::{Mutex, Arc};
use std::time::Duration;
use std::thread;
use std::time::Instant;
use std::io::{BufRead, BufReader};
use std::net::SocketAddr;
use std::ffi::OsStr;

use hyper::StatusCode;
use tempdir::TempDir;
use handlebars::Handlebars;
use serde_json::{self, Value};
use regex::Regex;

use cargo_shim::CargoResult;

use build::Backend;
use error::Error;
use utils::{
    read,
    read_bytes,
    find_cmd,
};
use http_utils::{
    SimpleServer,
    response_from_status,
    response_from_data
};
use chrome_devtools::{Connection, Reply, ReplyError, ConsoleApiCalledBody, ExceptionThrownBody};

const DEFAULT_TEST_INDEX_HTML: &'static str = r#"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" name="viewport" />
    <script>
        var __cargo_web = {};
        var Module = {};
        __cargo_web.status = new Promise( function( resolve ) { Module['onExit'] = resolve; } );
        Module['arguments'] = [{{#each arguments}} "{{{ this }}}", {{/each}}];
    </script>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>
"#;

pub fn test_in_chromium(
    backend: Backend,
    build: CargoResult,
    arg_passthrough: &Vec< &OsStr >,
    any_failure: &mut bool
) -> Result< (), Error > {
    let possible_commands =
        if cfg!( windows ) {
            &[ "chrome.exe" ][..]
        } else {
            &[ "chromium", "google-chrome", "google-chrome-stable" ][..]
        };

    let chromium_executable = find_cmd( possible_commands ).ok_or_else( || {
        Error::EnvironmentError( "you need to have either Chromium or Chrome installed and in your PATH to run the tests!".into() )
    })?;

    let app_js = Arc::new( Mutex::new( String::new() ) );
    let server_app_js = app_js.clone();
    let handlebars = Handlebars::new();
    let mut template_data = BTreeMap::new();
    let arg_passthrough: Vec<_> = arg_passthrough.iter().map( |arg| arg.to_str().unwrap() ).collect();
    template_data.insert( "arguments", arg_passthrough );
    let test_index = handlebars.render_template( DEFAULT_TEST_INDEX_HTML, &template_data ).unwrap();
    let app_wasm: Arc< Mutex< Option< Vec< u8 > > > > = Arc::new( Mutex::new( None ) );
    let wasm_url = Arc::new( Mutex::new( None ) );

    let server_app_wasm = app_wasm.clone();
    let server_wasm_url = wasm_url.clone();

    let (addr_tx, addr_rx) = channel();
    thread::spawn( move || {
        let server = SimpleServer::new(&"127.0.0.1:0".parse().unwrap(), move |request| {
            let path = request.path();
            if path == "/" || path == "index.html" {
                response_from_data( "text/html", test_index.clone().into_bytes() )
            } else if path == "/js/app.js" {
                let data = server_app_js.lock().unwrap().clone();
                response_from_data( "application/javascript", data.into_bytes() )
            } else {
                match *server_wasm_url.lock().unwrap() {
                    Some( ref server_wasm_url ) if path == *server_wasm_url => {
                        let data = server_app_wasm.lock().unwrap().as_ref().unwrap().clone();
                        response_from_data( "application/wasm", data )
                    },
                    _ => response_from_status(StatusCode::NotFound)
                }
            }
        });
        addr_tx.send(server.server_addr()).unwrap();
        server.run();
    });
    let server_address: SocketAddr = addr_rx.recv().unwrap();

    let artifact = build.artifacts().iter()
        .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
        .expect( "internal error: no .js file found" );

    if backend.is_any_wasm() {
        let wasm_artifact = build.artifacts().iter()
            .find( |artifact| artifact.extension().map( |ext| ext == "wasm" ).unwrap_or( false ) )
            .expect( "internal error: no .wasm file found" );

        *wasm_url.lock().unwrap() = Some( format!( "/{}", wasm_artifact.file_name().unwrap().to_str().unwrap() ) );
        *app_wasm.lock().unwrap() = Some( read_bytes( wasm_artifact ).unwrap() );
    }

    *app_js.lock().unwrap() = read( artifact ).unwrap();

    let tmpdir = TempDir::new( "cargo-web-chromium-profile" ).unwrap();
    let tmpdir = tmpdir.path().to_string_lossy();
    let mut command = Command::new( chromium_executable );
    command
        .arg( "--disable-gpu" )
        .arg( "--no-first-run" )
        .arg( "--no-sandbox" )
        .arg( "--disable-restore-session-state" )
        .arg( "--no-default-browser-check" )
        .arg( "--disable-java" )
        .arg( "--disable-client-side-phishing-detection" )
        .arg( "--headless" )
        .arg( "--remote-debugging-port=0" )
        .arg( format!( "--user-data-dir={}", tmpdir ) )
        .arg( "about:blank" );

    command
        .stdout( Stdio::null() )
        .stderr( Stdio::piped() )
        .stdin( Stdio::null() );

    debug!( "Launching chromium..." );
    let mut child = command.spawn()
        .map_err( |err| Error::RuntimeError( "cannot launch chromium".into(), err.into() ) )?;

    let stderr = BufReader::new( child.stderr.take().unwrap() );
    let devtools_regex = Regex::new( r"DevTools listening on (ws://[^:]+:\d+)" ).unwrap();
    let (url_tx, url_rx) = channel();
    thread::spawn( move || {
        for line in stderr.lines() {
            let line = match line {
                Ok( line ) => line,
                Err( _ ) => break
            };

            debug!( "Chromium stderr: {:?}", line );
            if let Some( captures ) = devtools_regex.captures( &line ) {
                let url = captures.get( 1 ).unwrap().as_str().to_owned();
                let _ = url_tx.send( url );
                break;
            }
        }
    });

    let url = url_rx.recv_timeout( Duration::from_secs( 10 ) )
        .map_err( |err| Error::RuntimeError( "timeout while waiting for chromium to start".into(), err.into() ) )?;

    debug!( "Chromium in listening on: {}", url );
    let mut connection = Connection::connect( &format!( "{}/json", url ) )
        .map_err( |err| Error::RuntimeError( "devtools connection to chromium failed".into(), err.into() ) )?;

    connection.send_cmd( "Page.enable", Value::Null );
    connection.send_cmd( "Runtime.enable", Value::Null );
    connection.send_cmd(
        "Page.navigate",
        json!({
            "url": format!( "http://localhost:{}", server_address.port() )
        })
    );

    let mut print_counter = 0;
    let mut finished = false;
    let start = Instant::now();
    let time_limit = Duration::from_secs( 60 );
    let mut get_status_req = None;
    loop {
        let elapsed = start.elapsed();
        if elapsed >= time_limit {
            break;
        }
        let remaining = time_limit - elapsed;

        let reply = connection.try_recv( Some( remaining ) );
        let reply = match reply {
            Ok( reply ) => reply,
            Err( ReplyError::Timeout ) => {
                if finished {
                    break;
                } else {
                    continue;
                }
            },
            Err( err ) => {
                return Err( Error::RuntimeError( "error while communicating with chromium".into(), err.into() ) );
            }
        };

        match reply {
            Reply::Result { ref id, ref body } if Some( *id ) == get_status_req => {
                finished = true;
                let status = body.get( "result" ).unwrap().get( "value" ).unwrap().as_u64().unwrap();
                if status != 0 {
                    eprintln!( "error: process exited with a status of {}", status );
                    *any_failure = true;
                }
                break;
            },
            Reply::Event { ref method, .. } if method == "Page.frameStoppedLoading" => {
                let id = connection.send_cmd(
                    "Runtime.evaluate",
                    json!({
                        "expression": "__cargo_web.status",
                        "awaitPromise": true
                    })
                );

                get_status_req = Some( id );
            },
            Reply::Event { ref method, ref body } if method == "Runtime.exceptionThrown" => {
                let body: ExceptionThrownBody = serde_json::from_value( body.clone() ).expect( "Failed to parse `Runtime.exceptionThrown` event" );
                eprintln!( "error: unhandled exception thrown" );
                if let Some( exception ) = body.exception_details.exception {
                    if let Some( description ) = exception.description {
                        eprintln!( "error:     {}", description );
                    }
                }
                if let Some( url ) = body.exception_details.url {
                    eprintln!( "error: source: {}:{}:{}", url, body.exception_details.line_number, body.exception_details.column_number );
                }
                // TODO: Better error message.
                *any_failure = true;
                finished = true;
                break;
            },
            Reply::Event { ref method, ref body } if method == "Runtime.consoleAPICalled" => {
                let body: ConsoleApiCalledBody = serde_json::from_value( body.clone() ).unwrap();
                match body.kind.as_str() {
                    "log" | "debug" | "info" | "error" | "warning" => {
                        let mut output = String::new();
                        for arg in body.args {
                            if !output.is_empty() {
                                output.push_str( " " );
                            }

                            if arg.kind == "string" {
                                output.push_str( arg.value.unwrap().as_str().unwrap() );
                            } else {
                                output.push_str( "<" );
                                if let Some( class_name ) = arg.class_name {
                                    output.push_str( &class_name );
                                } else {
                                    output.push_str( &arg.kind );
                                }
                                output.push_str( ">" );
                            }
                        }

                        if backend.is_emscripten() {
                            if print_counter == 0 && output.starts_with( "pre-main" ) {
                                continue;
                            } else if print_counter == 1 && output.trim().is_empty() {
                                continue;
                            }
                        }

                        println!( "{}", output );
                        print_counter += 1;
                    },
                    _ => {}
                }
            },
            Reply::Error { ref message, .. } => {
                return Err( Error::RuntimeError( "chromium returned an error".into(), message.clone().into() ) );
            },
            _ => {}
        }
    }

    if !finished {
        eprintln!( "error: tests timed out!" );
        *any_failure = true;
    }

    debug!( "Testing finished; waiting for chromium to die..." );
    child.kill().unwrap();
    child.wait().unwrap();

    Ok(())
}
<commit_msg>Check `chromium-browser` when searching for Chromium (#93)<commit_after>use std::collections::BTreeMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::channel;
use std::sync::{Mutex, Arc};
use std::time::Duration;
use std::thread;
use std::time::Instant;
use std::io::{BufRead, BufReader};
use std::net::SocketAddr;
use std::ffi::OsStr;

use hyper::StatusCode;
use tempdir::TempDir;
use handlebars::Handlebars;
use serde_json::{self, Value};
use regex::Regex;

use cargo_shim::CargoResult;

use build::Backend;
use error::Error;
use utils::{
    read,
    read_bytes,
    find_cmd,
};
use http_utils::{
    SimpleServer,
    response_from_status,
    response_from_data
};
use chrome_devtools::{Connection, Reply, ReplyError, ConsoleApiCalledBody, ExceptionThrownBody};

const DEFAULT_TEST_INDEX_HTML: &'static str = r#"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" name="viewport" />
    <script>
        var __cargo_web = {};
        var Module = {};
        __cargo_web.status = new Promise( function( resolve ) { Module['onExit'] = resolve; } );
        Module['arguments'] = [{{#each arguments}} "{{{ this }}}", {{/each}}];
    </script>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>
"#;

pub fn test_in_chromium(
    backend: Backend,
    build: CargoResult,
    arg_passthrough: &Vec< &OsStr >,
    any_failure: &mut bool
) -> Result< (), Error > {
    let possible_commands =
        if cfg!( windows ) {
            &[ "chrome.exe" ][..]
        } else {
            &[ "chromium", "chromium-browser", "google-chrome", "google-chrome-stable" ][..]
        };

    let chromium_executable = find_cmd( possible_commands ).ok_or_else( || {
        Error::EnvironmentError( "you need to have either Chromium or Chrome installed and in your PATH to run the tests!".into() )
    })?;

    let app_js = Arc::new( Mutex::new( String::new() ) );
    let server_app_js = app_js.clone();
    let handlebars = Handlebars::new();
    let mut template_data = BTreeMap::new();
    let arg_passthrough: Vec<_> = arg_passthrough.iter().map( |arg| arg.to_str().unwrap() ).collect();
    template_data.insert( "arguments", arg_passthrough );
    let test_index = handlebars.render_template( DEFAULT_TEST_INDEX_HTML, &template_data ).unwrap();
    let app_wasm: Arc< Mutex< Option< Vec< u8 > > > > = Arc::new( Mutex::new( None ) );
    let wasm_url = Arc::new( Mutex::new( None ) );

    let server_app_wasm = app_wasm.clone();
    let server_wasm_url = wasm_url.clone();

    let (addr_tx, addr_rx) = channel();
    thread::spawn( move || {
        let server = SimpleServer::new(&"127.0.0.1:0".parse().unwrap(), move |request| {
            let path = request.path();
            if path == "/" || path == "index.html" {
                response_from_data( "text/html", test_index.clone().into_bytes() )
            } else if path == "/js/app.js" {
                let data = server_app_js.lock().unwrap().clone();
                response_from_data( "application/javascript", data.into_bytes() )
            } else {
                match *server_wasm_url.lock().unwrap() {
                    Some( ref server_wasm_url ) if path == *server_wasm_url => {
                        let data = server_app_wasm.lock().unwrap().as_ref().unwrap().clone();
                        response_from_data( "application/wasm", data )
                    },
                    _ => response_from_status(StatusCode::NotFound)
                }
            }
        });
        addr_tx.send(server.server_addr()).unwrap();
        server.run();
    });
    let server_address: SocketAddr = addr_rx.recv().unwrap();

    let artifact = build.artifacts().iter()
        .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
        .expect( "internal error: no .js file found" );

    if backend.is_any_wasm() {
        let wasm_artifact = build.artifacts().iter()
            .find( |artifact| artifact.extension().map( |ext| ext == "wasm" ).unwrap_or( false ) )
            .expect( "internal error: no .wasm file found" );

        *wasm_url.lock().unwrap() = Some( format!( "/{}", wasm_artifact.file_name().unwrap().to_str().unwrap() ) );
        *app_wasm.lock().unwrap() = Some( read_bytes( wasm_artifact ).unwrap() );
    }

    *app_js.lock().unwrap() = read( artifact ).unwrap();

    let tmpdir = TempDir::new( "cargo-web-chromium-profile" ).unwrap();
    let tmpdir = tmpdir.path().to_string_lossy();
    let mut command = Command::new( chromium_executable );
    command
        .arg( "--disable-gpu" )
        .arg( "--no-first-run" )
        .arg( "--no-sandbox" )
        .arg( "--disable-restore-session-state" )
        .arg( "--no-default-browser-check" )
        .arg( "--disable-java" )
        .arg( "--disable-client-side-phishing-detection" )
        .arg( "--headless" )
        .arg( "--remote-debugging-port=0" )
        .arg( format!( "--user-data-dir={}", tmpdir ) )
        .arg( "about:blank" );

    command
        .stdout( Stdio::null() )
        .stderr( Stdio::piped() )
        .stdin( Stdio::null() );

    debug!( "Launching chromium..." );
    let mut child = command.spawn()
        .map_err( |err| Error::RuntimeError( "cannot launch chromium".into(), err.into() ) )?;

    let stderr = BufReader::new( child.stderr.take().unwrap() );
    let devtools_regex = Regex::new( r"DevTools listening on (ws://[^:]+:\d+)" ).unwrap();
    let (url_tx, url_rx) = channel();
    thread::spawn( move || {
        for line in stderr.lines() {
            let line = match line {
                Ok( line ) => line,
                Err( _ ) => break
            };

            debug!( "Chromium stderr: {:?}", line );
            if let Some( captures ) = devtools_regex.captures( &line ) {
                let url = captures.get( 1 ).unwrap().as_str().to_owned();
                let _ = url_tx.send( url );
                break;
            }
        }
    });

    let url = url_rx.recv_timeout( Duration::from_secs( 10 ) )
        .map_err( |err| Error::RuntimeError( "timeout while waiting for chromium to start".into(), err.into() ) )?;

    debug!( "Chromium in listening on: {}", url );
    let mut connection = Connection::connect( &format!( "{}/json", url ) )
        .map_err( |err| Error::RuntimeError( "devtools connection to chromium failed".into(), err.into() ) )?;

    connection.send_cmd( "Page.enable", Value::Null );
    connection.send_cmd( "Runtime.enable", Value::Null );
    connection.send_cmd(
        "Page.navigate",
        json!({
            "url": format!( "http://localhost:{}", server_address.port() )
        })
    );

    let mut print_counter = 0;
    let mut finished = false;
    let start = Instant::now();
    let time_limit = Duration::from_secs( 60 );
    let mut get_status_req = None;
    loop {
        let elapsed = start.elapsed();
        if elapsed >= time_limit {
            break;
        }
        let remaining = time_limit - elapsed;

        let reply = connection.try_recv( Some( remaining ) );
        let reply = match reply {
            Ok( reply ) => reply,
            Err( ReplyError::Timeout ) => {
                if finished {
                    break;
                } else {
                    continue;
                }
            },
            Err( err ) => {
                return Err( Error::RuntimeError( "error while communicating with chromium".into(), err.into() ) );
            }
        };

        match reply {
            Reply::Result { ref id, ref body } if Some( *id ) == get_status_req => {
                finished = true;
                let status = body.get( "result" ).unwrap().get( "value" ).unwrap().as_u64().unwrap();
                if status != 0 {
                    eprintln!( "error: process exited with a status of {}", status );
                    *any_failure = true;
                }
                break;
            },
            Reply::Event { ref method, .. } if method == "Page.frameStoppedLoading" => {
                let id = connection.send_cmd(
                    "Runtime.evaluate",
                    json!({
                        "expression": "__cargo_web.status",
                        "awaitPromise": true
                    })
                );

                get_status_req = Some( id );
            },
            Reply::Event { ref method, ref body } if method == "Runtime.exceptionThrown" => {
                let body: ExceptionThrownBody = serde_json::from_value( body.clone() ).expect( "Failed to parse `Runtime.exceptionThrown` event" );
                eprintln!( "error: unhandled exception thrown" );
                if let Some( exception ) = body.exception_details.exception {
                    if let Some( description ) = exception.description {
                        eprintln!( "error:     {}", description );
                    }
                }
                if let Some( url ) = body.exception_details.url {
                    eprintln!( "error: source: {}:{}:{}", url, body.exception_details.line_number, body.exception_details.column_number );
                }
                // TODO: Better error message.
                *any_failure = true;
                finished = true;
                break;
            },
            Reply::Event { ref method, ref body } if method == "Runtime.consoleAPICalled" => {
                let body: ConsoleApiCalledBody = serde_json::from_value( body.clone() ).unwrap();
                match body.kind.as_str() {
                    "log" | "debug" | "info" | "error" | "warning" => {
                        let mut output = String::new();
                        for arg in body.args {
                            if !output.is_empty() {
                                output.push_str( " " );
                            }

                            if arg.kind == "string" {
                                output.push_str( arg.value.unwrap().as_str().unwrap() );
                            } else {
                                output.push_str( "<" );
                                if let Some( class_name ) = arg.class_name {
                                    output.push_str( &class_name );
                                } else {
                                    output.push_str( &arg.kind );
                                }
                                output.push_str( ">" );
                            }
                        }

                        if backend.is_emscripten() {
                            if print_counter == 0 && output.starts_with( "pre-main" ) {
                                continue;
                            } else if print_counter == 1 && output.trim().is_empty() {
                                continue;
                            }
                        }

                        println!( "{}", output );
                        print_counter += 1;
                    },
                    _ => {}
                }
            },
            Reply::Error { ref message, .. } => {
                return Err( Error::RuntimeError( "chromium returned an error".into(), message.clone().into() ) );
            },
            _ => {}
        }
    }

    if !finished {
        eprintln!( "error: tests timed out!" );
        *any_failure = true;
    }

    debug!( "Testing finished; waiting for chromium to die..." );
    child.kill().unwrap();
    child.wait().unwrap();

    Ok(())
}
<|endoftext|>


<commit_before>use gl;
use gl::types::*;
use texture::{Texture2dBinder, TextureCubeMapBinder};
use types::GLError;

unsafe fn _active_texture(idx: u32) {
    gl::ActiveTexture(gl::TEXTURE0 + (idx as GLenum));
    dbg_gl_error! {
        GLError::InvalidEnum => "`texture` is out of bounds (expected to be GL_TEXTUREi, 0 <= i < GL_MAX_TEXTURE_IMAGE_UNITS)",
        _ => "Unknown error"
    }
}

pub trait TextureUnit {
    fn idx() -> u32;

    fn active(&mut self) -> TextureUnitBinding {
        unsafe {
            _active_texture(Self::idx());
            TextureUnitBinding::current_at_idx(Self::idx())
        }
    }
}

// TODO: Use a macro, or const generic parameters:
// https://github.com/rust-lang/rfcs/issues/273
// https://github.com/rust-lang/rfcs/issues/1038
pub struct TextureUnit0;
pub struct TextureUnit1;
pub struct TextureUnit2;
pub struct TextureUnit3;
pub struct TextureUnit4;
pub struct TextureUnit5;
pub struct TextureUnit6;
pub struct TextureUnit7;

impl TextureUnit for TextureUnit0 { fn idx() -> u32 { 0 } }
impl TextureUnit for TextureUnit1 { fn idx() -> u32 { 1 } }
impl TextureUnit for TextureUnit2 { fn idx() -> u32 { 2 } }
impl TextureUnit for TextureUnit3 { fn idx() -> u32 { 3 } }
impl TextureUnit for TextureUnit4 { fn idx() -> u32 { 4 } }
impl TextureUnit for TextureUnit5 { fn idx() -> u32 { 5 } }
impl TextureUnit for TextureUnit6 { fn idx() -> u32 { 6 } }
impl TextureUnit for TextureUnit7 { fn idx() -> u32 { 7 } }

// NOTE: Ensure the number of each texture unit matches its index in the tuple
pub struct TextureUnits(pub TextureUnit0, pub TextureUnit1, pub TextureUnit2,
                        pub TextureUnit3, pub TextureUnit4, pub TextureUnit5,
                        pub TextureUnit6, pub TextureUnit7);

impl TextureUnits {
    pub unsafe fn current() -> TextureUnits {
        TextureUnits(TextureUnit0, TextureUnit1, TextureUnit2, TextureUnit3,
                     TextureUnit4, TextureUnit5, TextureUnit6, TextureUnit7)
    }

    pub unsafe fn active_nth(&self, idx: u32) -> TextureUnitBinding {
        _active_texture(idx);
        TextureUnitBinding::current_at_idx(idx)
    }
}

pub struct TextureUnitBinding {
    pub texture_2d: Texture2dBinder,
    pub texture_cube_map: TextureCubeMapBinder
}

impl TextureUnitBinding {
    unsafe fn current_at_idx(_idx: u32) -> Self {
        TextureUnitBinding {
            texture_2d: Texture2dBinder,
            texture_cube_map: TextureCubeMapBinder
        }
    }
}

#[macro_export]
macro_rules! bind_texture_cube_map {
    ($gl_tex_unit:expr, $texture:expr) => {
        $gl_tex_unit.texture_cube_map.bind($texture)
    }
}

#[macro_export]
macro_rules! bind_texture_2d {
    ($gl_tex_unit:expr, $texture:expr) => {
        $gl_tex_unit.texture_2d.bind($texture)
    }
}
<commit_msg>Add `idx` field to `TextureUnitBinding`<commit_after>use gl;
use gl::types::*;
use texture::{Texture2dBinder, TextureCubeMapBinder};
use types::GLError;

unsafe fn _active_texture(idx: u32) {
    gl::ActiveTexture(gl::TEXTURE0 + (idx as GLenum));
    dbg_gl_error! {
        GLError::InvalidEnum => "`texture` is out of bounds (expected to be GL_TEXTUREi, 0 <= i < GL_MAX_TEXTURE_IMAGE_UNITS)",
        _ => "Unknown error"
    }
}

pub trait TextureUnit {
    fn idx() -> u32;

    fn active(&mut self) -> TextureUnitBinding {
        unsafe {
            _active_texture(Self::idx());
            TextureUnitBinding::current_at_idx(Self::idx())
        }
    }
}

// TODO: Use a macro, or const generic parameters:
// https://github.com/rust-lang/rfcs/issues/273
// https://github.com/rust-lang/rfcs/issues/1038
pub struct TextureUnit0;
pub struct TextureUnit1;
pub struct TextureUnit2;
pub struct TextureUnit3;
pub struct TextureUnit4;
pub struct TextureUnit5;
pub struct TextureUnit6;
pub struct TextureUnit7;

impl TextureUnit for TextureUnit0 { fn idx() -> u32 { 0 } }
impl TextureUnit for TextureUnit1 { fn idx() -> u32 { 1 } }
impl TextureUnit for TextureUnit2 { fn idx() -> u32 { 2 } }
impl TextureUnit for TextureUnit3 { fn idx() -> u32 { 3 } }
impl TextureUnit for TextureUnit4 { fn idx() -> u32 { 4 } }
impl TextureUnit for TextureUnit5 { fn idx() -> u32 { 5 } }
impl TextureUnit for TextureUnit6 { fn idx() -> u32 { 6 } }
impl TextureUnit for TextureUnit7 { fn idx() -> u32 { 7 } }

// NOTE: Ensure the number of each texture unit matches its index in the tuple
pub struct TextureUnits(pub TextureUnit0, pub TextureUnit1, pub TextureUnit2,
                        pub TextureUnit3, pub TextureUnit4, pub TextureUnit5,
                        pub TextureUnit6, pub TextureUnit7);

impl TextureUnits {
    pub unsafe fn current() -> TextureUnits {
        TextureUnits(TextureUnit0, TextureUnit1, TextureUnit2, TextureUnit3,
                     TextureUnit4, TextureUnit5, TextureUnit6, TextureUnit7)
    }

    pub unsafe fn active_nth(&self, idx: u32) -> TextureUnitBinding {
        _active_texture(idx);
        TextureUnitBinding::current_at_idx(idx)
    }
}

pub struct TextureUnitBinding {
    idx: u32,
    pub texture_2d: Texture2dBinder,
    pub texture_cube_map: TextureCubeMapBinder
}

impl TextureUnitBinding {
    unsafe fn current_at_idx(idx: u32) -> Self {
        TextureUnitBinding {
            idx: idx,
            texture_2d: Texture2dBinder,
            texture_cube_map: TextureCubeMapBinder
        }
    }
}

#[macro_export]
macro_rules! bind_texture_cube_map {
    ($gl_tex_unit:expr, $texture:expr) => {
        $gl_tex_unit.texture_cube_map.bind($texture)
    }
}

#[macro_export]
macro_rules! bind_texture_2d {
    ($gl_tex_unit:expr, $texture:expr) => {
        $gl_tex_unit.texture_2d.bind($texture)
    }
}
<|endoftext|>


<commit_before>extern crate serde_derive;

use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

use chrono::{DateTime, Utc};
use clap::{App, Arg, ArgMatches, SubCommand};
use glob::glob;
use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Debug, Clone)]
struct RepoConfig {
    path: String,
    #[serde(default = "default_false")]
    commit: bool,
    #[serde(default = "default_false")]
    sync_updates: bool,
}

fn default_false() -> bool {
    false
}

#[derive(Deserialize, Debug)]
struct Supfile {
    repo: RepoConfig,
    editor: Option<String>,
}

impl Supfile {
    pub fn from(str: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(&str)
    }

    pub fn find() -> PathBuf {
        // TODO: pull in XDG crate for paths
        let default_dir = "~/.config/sup/";

        ["./", default_dir]
            .iter()
            .map(|dir| Path::new(dir).join("Supfile"))
            .find(|p| p.exists())
            .unwrap_or_else(|| {
                // TODO: offer to create path somehow
                panic!("can't find supfile")
            })
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
enum TaskState {
    Todo,
    InProgress,
    Complete,
    Canceled,
    Deferred { until: DateTime<Utc> },
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
enum Action {
    Create {
        task: String,
        #[serde(default)]
        notes: Option<String>,
        #[serde(default)]
        tags: Vec<String>,
    },
    EditTags {
        add: Vec<String>,
        remove: Vec<String>,
    },
    EditTask(String),
    EditNotes(Option<String>),
    EditState(TaskState),
}

#[derive(Deserialize, Serialize)]
struct TaskUpdate {
    id: String,
    timestamp: DateTime<Utc>,
    action: Action,
}

impl TaskUpdate {
    fn from_str(line: &str) -> serde_json::Result<Self> {
        serde_json::from_str(line).map_err(|e| {
            println!("nuts: {:?}", e);
            e
        })
    }
}

#[derive(Clone, Debug)]
struct Task {
    id: String,
    created_at: DateTime<Utc>,
    completed_at: Option<DateTime<Utc>>,
    state: TaskState,
    task: String,
    notes: Option<String>,
    tags: HashSet<String>,
}

impl Task {
    fn apply(&mut self, update: &TaskUpdate) {
        match &update.action {
            Action::Create { task, notes, tags } => {
                self.task = task.clone();
                self.notes = notes.clone();
                for tag in tags.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTags { add, remove } => {
                for tag in remove.iter() {
                    self.tags.remove(tag);
                }

                for tag in add.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTask(task) => self.task = task.clone(),
            Action::EditNotes(notes) => self.notes = notes.clone(),
            Action::EditState(state) => {
                self.state = state.clone();
                if state == &TaskState::Complete {
                    self.completed_at = Some(update.timestamp);
                }
            }
        }
    }
}

impl From<&TaskUpdate> for Task {
    fn from(update: &TaskUpdate) -> Self {
        let mut task = Task {
            id: update.id.clone(),
            created_at: update.timestamp.clone(),
            completed_at: None,
            state: TaskState::Todo,
            task: "untitled".to_string(),
            notes: None,
            tags: HashSet::new(),
        };

        task.apply(update);
        return task;
    }
}

#[derive(Debug)]
struct SupdateLog {
    name: String,
    file: BufReader<File>,
}

impl SupdateLog {
    fn new(path: &Path) -> Self {
        let name = path
            .file_name()
            .expect("need file, not directory")
            .to_string_lossy()
            .into();

        let file = File::open(path).expect("could not open file");
        // TODO: Support versioning in line

        SupdateLog {
            name,
            file: BufReader::new(file),
        }
    }
}

impl Iterator for SupdateLog {
    type Item = TaskUpdate;

    fn next(&mut self) -> Option<TaskUpdate> {
        let mut line = String::new();
        return match self.file.read_line(&mut line) {
            // End of file
            Ok(0) => None,

            Ok(_) => {
                println!("line = {:?}", line);
                // TODO: alert when we don't succeed at parsing
                TaskUpdate::from_str(&line).ok()
            }

            Err(e) => {
                println!("Failed to read line: {:?}", e);
                None
            }
        };
    }
}

/**
 * A Repository represents a directory containing SupdateLogs.
 *
 * The directory structure is something like this:
 *
 * repo/
 *   2019/
 *     08/
 *       20190801_235959.sup
 *       20190802_003030.sup
 *       20190802_123030.sup
 *
 * TODO: Support concept of `rollup` files to put an upper limit on
 * how far we need to look back to fully reconstruct the state of
 * everything.
 */

struct Repository {
    config: RepoConfig,
}

impl Repository {
    fn new(cfg: RepoConfig) -> Self {
        // TODO: Handle error, return Result
        if !Path::new(&cfg.path).exists() {
            panic!("given repository does not exist")
        }

        Repository { config: cfg }
    }

    fn open_journal(&self) -> Journal {
        let updates = self.read_updates();
        self.build_journal(updates)
    }

    fn build_journal(&self, updates: Vec<SupdateLog>) -> Journal {
        let mut journal = Journal::new();

        // Reverse ordering so that we consume oldest elements first.
        for log in updates.into_iter().rev() {
            println!("reading: {:?}", log);

            for ref update in log {
                journal.add(update);
            }
        }

        journal
    }

    fn read_updates(&self) -> Vec<SupdateLog> {
        let path = Path::new(&self.config.path).join("**/*.sup");

        // TODO: This is really wasteful. Make this an iterator
        // Get list of all files in the repo
        let mut updates = glob(path.to_str().unwrap())
            .expect("glob failed")
            .filter_map(|f| f.map(|p| SupdateLog::new(&p)).ok())
            .collect::<Vec<SupdateLog>>();

        // We want sorted (chronological, desc) order, based on file name.
        updates.sort_by(|a, b| b.name.cmp(&a.name));
        return updates;
    }
}

/**
 * A collection of Updates.
 *
 * Represents the cumulative, "rolled up" set of task updates.
 */
#[derive(Debug)]
struct Journal {
    /// id -> task
    entries: BTreeMap<String, Task>,
}

impl Journal {
    fn new() -> Self {
        return Self {
            entries: BTreeMap::new(),
        };
    }

    fn add(&mut self, update: &TaskUpdate) {
        let task = self.entries.get_mut(&update.id);

        match task {
            Some(t) => t.apply(update),
            None => {
                self.entries.insert(update.id.clone(), Task::from(update));
            }
        }
    }
}

struct SupApp {
    matches: ArgMatches<'static>,
}

impl SupApp {
    fn new() -> Self {
        Self {
            matches: Self::parse_args(),
        }
    }

    fn parse_args<'a>() -> ArgMatches<'a> {
        App::new("sup")
            .version("0.0.0")
            .about("Keep track of what's up.")
            .arg(
                Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .value_name("FILE")
                    .takes_value(true)
                    .help("Set custom location for Supfile"),
            )
            .subcommand(
                SubCommand::with_name("show")
                    .about("asdf")
                    .arg(Arg::with_name("DATE").takes_value(true).help("asdf")),
            )
            .subcommand(
                SubCommand::with_name("serve")
                    .about("launch web interface")
                    .arg(
                        Arg::with_name("port")
                            .short("p")
                            .long("port")
                            .takes_value(true),
                    ),
            )
            .get_matches()
    }

    fn load_config(&self) -> Result<Supfile, Box<dyn Error>> {
        let path = self
            .matches
            .value_of("config")
            .map(|p| Path::new(p).to_path_buf())
            .unwrap_or_else(Supfile::find);

        let bytes = std::fs::read_to_string(path.as_os_str())?;
        Ok(Supfile::from(&bytes)?)
    }

    fn run(&self) -> Result<(), Box<dyn Error>> {
        let cfg = self.load_config()?;

        match self.matches.subcommand() {
            ("show", Some(m)) => println!("TODO: show {:?}", m),
            ("serve", Some(m)) => println!("TODO: serve {:?}", m),
            _ => {
                println!("add update");

                run_add_update(&cfg)?;
            }
        };

        Ok(())
    }
}

fn run_add_update(cfg: &Supfile) -> Result<(), Box<dyn Error>> {
    let repo = Repository::new(cfg.repo.clone());
    let journal = repo.open_journal();

    println!("journal: {:?}", journal);

    Ok(())
}

// TODO: Investiage this instead of Box<Error>
// https://github.com/rust-lang-nursery/failure
fn main() {
    let app = SupApp::new();
    match app.run() {
        Err(e) => println!("oh no! {:?}", e),
        Ok(_) => (),
    };
}
<commit_msg>Work on formatting<commit_after>extern crate serde_derive;

use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

use chrono::{DateTime, Utc};
use clap::{App, Arg, ArgMatches, SubCommand};
use glob::glob;
use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Debug, Clone)]
struct RepoConfig {
    path: String,
    #[serde(default = "default_false")]
    commit: bool,
    #[serde(default = "default_false")]
    sync_updates: bool,
}

fn default_false() -> bool {
    false
}

#[derive(Deserialize, Debug)]
struct Supfile {
    repo: RepoConfig,
    editor: Option<String>,
}

impl Supfile {
    pub fn from(str: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(&str)
    }

    pub fn find() -> PathBuf {
        // TODO: pull in XDG crate for paths
        let default_dir = "~/.config/sup/";

        ["./", default_dir]
            .iter()
            .map(|dir| Path::new(dir).join("Supfile"))
            .find(|p| p.exists())
            .unwrap_or_else(|| {
                // TODO: offer to create path somehow
                panic!("can't find supfile")
            })
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
enum TaskState {
    Todo,
    InProgress,
    Complete,
    Canceled,
    Deferred,
}

impl TaskState {
    fn is_open(self) -> bool {
        match self {
            TaskState::Todo | TaskState::InProgress | TaskState::Deferred => true,
            _ => false,
        }
    }
}

impl std::fmt::Display for TaskState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let value = match self {
            TaskState::Todo => " ",
            TaskState::InProgress => "•",
            TaskState::Complete => "✔",
            TaskState::Canceled => "-",
            TaskState::Deferred => "⟫",
        };
        write!(f, "{}", value)
    }
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
enum Action {
    Create {
        task: String,
        #[serde(default)]
        notes: Option<String>,
        #[serde(default)]
        tags: Vec<String>,
    },
    EditTags {
        add: Vec<String>,
        remove: Vec<String>,
    },
    EditTask(String),
    EditNotes(Option<String>),
    EditState(TaskState),
}

#[derive(Deserialize, Serialize)]
struct TaskUpdate {
    id: String,
    timestamp: DateTime<Utc>,
    action: Action,
}

impl TaskUpdate {
    fn from_str(line: &str) -> serde_json::Result<Self> {
        serde_json::from_str(line).map_err(|e| {
            println!("nuts: {:?}", e);
            e
        })
    }
}

#[derive(Clone, Debug)]
struct Task {
    id: String,
    created_at: DateTime<Utc>,
    completed_at: Option<DateTime<Utc>>,
    state: TaskState,
    task: String,
    notes: Option<String>,
    tags: HashSet<String>,
}

impl Task {
    fn apply(&mut self, update: &TaskUpdate) {
        match &update.action {
            Action::Create { task, notes, tags } => {
                self.task = task.clone();
                self.notes = notes.clone();
                for tag in tags.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTags { add, remove } => {
                for tag in remove.iter() {
                    self.tags.remove(tag);
                }

                for tag in add.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTask(task) => self.task = task.clone(),
            Action::EditNotes(notes) => self.notes = notes.clone(),
            Action::EditState(state) => {
                self.state = state.clone();
                if state == &TaskState::Complete {
                    self.completed_at = Some(update.timestamp);
                }
            }
        }
    }

    fn summary(&self) -> String {
        format!("{} [{}] {}", self.id, self.state, self.task)
    }
}

impl From<&TaskUpdate> for Task {
    fn from(update: &TaskUpdate) -> Self {
        let mut task = Task {
            id: update.id.clone(),
            created_at: update.timestamp.clone(),
            completed_at: None,
            state: TaskState::Todo,
            task: "untitled".to_string(),
            notes: None,
            tags: HashSet::new(),
        };

        task.apply(update);
        return task;
    }
}

#[derive(Debug)]
struct SupdateLog {
    name: String,
    file: BufReader<File>,
}

impl SupdateLog {
    fn new(path: &Path) -> Self {
        let name = path
            .file_name()
            .expect("need file, not directory")
            .to_string_lossy()
            .into();

        let file = File::open(path).expect("could not open file");
        // TODO: Support versioning in line

        SupdateLog {
            name,
            file: BufReader::new(file),
        }
    }
}

impl Iterator for SupdateLog {
    type Item = TaskUpdate;

    fn next(&mut self) -> Option<TaskUpdate> {
        let mut line = String::new();
        return match self.file.read_line(&mut line) {
            // End of file
            Ok(0) => None,

            Ok(_) => {
                //println!("line = {:?}", line);
                // TODO: alert when we don't succeed at parsing
                TaskUpdate::from_str(&line).ok()
            }

            Err(e) => {
                println!("Failed to read line: {:?}", e);
                None
            }
        };
    }
}

/**
 * A Repository represents a directory containing SupdateLogs.
 *
 * The directory structure is something like this:
 *
 * repo/
 *   2019/
 *     08/
 *       20190801_235959.sup
 *       20190802_003030.sup
 *       20190802_123030.sup
 *
 * TODO: Support concept of `rollup` files to put an upper limit on
 * how far we need to look back to fully reconstruct the state of
 * everything.
 */

struct Repository {
    config: RepoConfig,
}

impl Repository {
    fn new(cfg: RepoConfig) -> Self {
        // TODO: Handle error, return Result
        if !Path::new(&cfg.path).exists() {
            panic!("given repository does not exist")
        }

        Repository { config: cfg }
    }

    fn open_journal(&self) -> Journal {
        let updates = self.read_updates();
        self.build_journal(updates)
    }

    fn build_journal(&self, updates: Vec<SupdateLog>) -> Journal {
        let mut journal = Journal::new();

        // Reverse ordering so that we consume oldest elements first.
        for log in updates.into_iter().rev() {
            println!("reading: {:?}", log);

            for ref update in log {
                journal.add(update);
            }
        }

        journal
    }

    fn read_updates(&self) -> Vec<SupdateLog> {
        let path = Path::new(&self.config.path).join("**/*.sup");

        // TODO: This is really wasteful. Make this an iterator
        // Get list of all files in the repo
        let mut updates = glob(path.to_str().unwrap())
            .expect("glob failed")
            .filter_map(|f| f.map(|p| SupdateLog::new(&p)).ok())
            .collect::<Vec<SupdateLog>>();

        // We want sorted (chronological, desc) order, based on file name.
        updates.sort_by(|a, b| b.name.cmp(&a.name));
        return updates;
    }
}

/**
 * A collection of Updates.
 *
 * Represents the cumulative, "rolled up" set of task updates.
 */
#[derive(Debug)]
struct Journal {
    /// id -> task
    entries: BTreeMap<String, Task>,
}

impl Journal {
    fn new() -> Self {
        return Self {
            entries: BTreeMap::new(),
        };
    }

    fn add(&mut self, update: &TaskUpdate) {
        let task = self.entries.get_mut(&update.id);

        match task {
            Some(t) => t.apply(update),
            None => {
                self.entries.insert(update.id.clone(), Task::from(update));
            }
        }
    }

    // TODO: Hack until a custom iterator is implemented.
    fn for_each(&self, f: &Fn(&Task) -> ()) {
        self.entries.values().for_each(f)
    }
}

struct SupCli {
    matches: ArgMatches<'static>,
}

impl SupCli {
    fn new() -> Self {
        Self {
            matches: Self::parse_args(),
        }
    }

    fn parse_args<'a>() -> ArgMatches<'a> {
        App::new("sup")
            .version("0.0.0")
            .about("Keep track of what's up.")
            .arg(
                Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .value_name("FILE")
                    .takes_value(true)
                    .help("Set custom location for Supfile"),
            )
            .subcommand(
                SubCommand::with_name("todo")
                    .about("create a new todo item")
                    .arg(Arg::with_name("TASK").takes_value(true).multiple(true)),
            )
            .subcommand(
                SubCommand::with_name("show")
                    .about("asdf")
                    .arg(Arg::with_name("DATE").takes_value(true).help("asdf")),
            )
            .subcommand(
                SubCommand::with_name("serve")
                    .about("launch web interface")
                    .arg(
                        Arg::with_name("port")
                            .short("p")
                            .long("port")
                            .takes_value(true),
                    ),
            )
            .get_matches()
    }

    fn load_config(&self) -> Result<Supfile, Box<dyn Error>> {
        let path = self
            .matches
            .value_of("config")
            .map(|p| Path::new(p).to_path_buf())
            .unwrap_or_else(Supfile::find);

        let bytes = std::fs::read_to_string(path.as_os_str())?;
        Ok(Supfile::from(&bytes)?)
    }

    fn run(&self) -> Result<(), Box<dyn Error>> {
        let cfg = self.load_config().expect("error in config file");
        let app = SupApp::new(&cfg);

        match self.matches.subcommand() {
            ("show", Some(m)) => println!("TODO: show {:?}", m),
            ("serve", Some(m)) => println!("TODO: serve {:?}", m),
            ("todo", Some(m)) => {
                println!("here's the todo: {:?}", m);
                app.run_add_update()?;
            }
            _ => {
                println!("add update");
                app.run_show_updates()?;
            }
        };

        Ok(())
    }
}

struct SupApp {
    repo: Repository,
    journal: Journal,
}

impl SupApp {
    fn new(cfg: &Supfile) -> Self {
        let repo = Repository::new(cfg.repo.clone());
        let journal = repo.open_journal();

        Self { repo, journal }
    }

    fn run_add_update(&self) -> Result<(), Box<dyn Error>> {
        println!("journal: {:?}", self.journal);
        Ok(())
    }

    fn run_show_updates(&self) -> Result<(), Box<dyn Error>> {
        self.journal.for_each(&|task| {
            println!("{}", task.summary());
        });

        Ok(())
    }
}

// TODO: Investiage this instead of Box<Error>
// https://github.com/rust-lang-nursery/failure
fn main() {
    let app = SupCli::new();
    match app.run() {
        Err(e) => println!("oh no! {:?}", e),
        Ok(_) => (),
    };
}
<|endoftext|>


<commit_before>#[cfg(feature = "tokio-runtime")]
use tokio::net::{TcpListener, TcpStream};

#[cfg(feature = "async-std-runtime")]
use async_std::net::{TcpListener, TcpStream};

use super::make_framed;
use super::AcceptStopHandle;
use crate::async_rt;
use crate::codec::FramedIo;
use crate::endpoint::{Endpoint, Host, Port};
use crate::task_handle::TaskHandle;
use crate::ZmqResult;

use futures::{select, FutureExt};

pub(crate) async fn connect(host: &Host, port: Port) -> ZmqResult<(FramedIo, Endpoint)> {
    let raw_socket = TcpStream::connect((host.to_string().as_str(), port)).await?;
    let peer_addr = raw_socket.peer_addr()?;

    Ok((make_framed(raw_socket), Endpoint::from_tcp_addr(peer_addr)))
}

pub(crate) async fn begin_accept<T>(
    mut host: Host,
    port: Port,
    cback: impl Fn(ZmqResult<(FramedIo, Endpoint)>) -> T + Send + 'static,
) -> ZmqResult<(Endpoint, AcceptStopHandle)>
where
    T: std::future::Future<Output = ()> + Send + 'static,
{
    let listener = TcpListener::bind((host.to_string().as_str(), port)).await?;
    let resolved_addr = listener.local_addr()?;
    let (stop_channel, stop_callback) = futures::channel::oneshot::channel::<()>();
    let task_handle = async_rt::task::spawn(async move {
        let mut stop_callback = stop_callback.fuse();
        loop {
            select! {
                incoming = listener.accept().fuse() => {
                    let maybe_accepted: Result<_, _> = incoming.map(|(raw_socket, remote_addr)| {
                        (make_framed(raw_socket), Endpoint::from_tcp_addr(remote_addr))
                    }).map_err(|err| err.into());
                    async_rt::task::spawn(cback(maybe_accepted));
                },
                _ = stop_callback => {
                    break
                }
            }
        }
        Ok(())
    });
    debug_assert_ne!(resolved_addr.port(), 0);
    let port = resolved_addr.port();
    let resolved_host: Host = resolved_addr.ip().into();
    if let Host::Ipv4(ip) = host {
        debug_assert_eq!(ip, resolved_addr.ip());
        host = resolved_host;
    } else if let Host::Ipv6(ip) = host {
        debug_assert_eq!(ip, resolved_addr.ip());
        host = resolved_host;
    }
    Ok((
        Endpoint::Tcp(host, port),
        AcceptStopHandle(TaskHandle::new(stop_channel, task_handle)),
    ))
}
<commit_msg>disable Nagle on all sockets<commit_after>#[cfg(feature = "tokio-runtime")]
use tokio::net::{TcpListener, TcpStream};

#[cfg(feature = "async-std-runtime")]
use async_std::net::{TcpListener, TcpStream};

use super::make_framed;
use super::AcceptStopHandle;
use crate::async_rt;
use crate::codec::FramedIo;
use crate::endpoint::{Endpoint, Host, Port};
use crate::task_handle::TaskHandle;
use crate::ZmqResult;

use futures::{select, FutureExt};

pub(crate) async fn connect(host: &Host, port: Port) -> ZmqResult<(FramedIo, Endpoint)> {
    let raw_socket = TcpStream::connect((host.to_string().as_str(), port)).await?;
    raw_socket.set_nodelay(true)?;
    let peer_addr = raw_socket.peer_addr()?;

    Ok((make_framed(raw_socket), Endpoint::from_tcp_addr(peer_addr)))
}

pub(crate) async fn begin_accept<T>(
    mut host: Host,
    port: Port,
    cback: impl Fn(ZmqResult<(FramedIo, Endpoint)>) -> T + Send + 'static,
) -> ZmqResult<(Endpoint, AcceptStopHandle)>
where
    T: std::future::Future<Output = ()> + Send + 'static,
{
    let listener = TcpListener::bind((host.to_string().as_str(), port)).await?;
    let resolved_addr = listener.local_addr()?;
    let (stop_channel, stop_callback) = futures::channel::oneshot::channel::<()>();
    let task_handle = async_rt::task::spawn(async move {
        let mut stop_callback = stop_callback.fuse();
        loop {
            select! {
                incoming = listener.accept().fuse() => {
                    let maybe_accepted: Result<_, _> = incoming.and_then(|(raw_socket, remote_addr)|{
                        raw_socket.set_nodelay(true).map(|_| {
                            (raw_socket, remote_addr)
                        })
                    }).map(|(raw_socket, remote_addr)| {
                        (make_framed(raw_socket), Endpoint::from_tcp_addr(remote_addr))
                    }).map_err(|err| err.into());
                    async_rt::task::spawn(cback(maybe_accepted));
                },
                _ = stop_callback => {
                    break
                }
            }
        }
        Ok(())
    });
    debug_assert_ne!(resolved_addr.port(), 0);
    let port = resolved_addr.port();
    let resolved_host: Host = resolved_addr.ip().into();
    if let Host::Ipv4(ip) = host {
        debug_assert_eq!(ip, resolved_addr.ip());
        host = resolved_host;
    } else if let Host::Ipv6(ip) = host {
        debug_assert_eq!(ip, resolved_addr.ip());
        host = resolved_host;
    }
    Ok((
        Endpoint::Tcp(host, port),
        AcceptStopHandle(TaskHandle::new(stop_channel, task_handle)),
    ))
}
<|endoftext|>


<commit_before>#![allow(non_snake_case)]

#[macro_use]
extern crate jsontests_derive;
extern crate jsontests;
#[macro_use]
extern crate criterion;

use criterion::Criterion;
use std::time::Duration;

#[derive(JsonTests)]
#[directory = "jsontests/res/files/eth/VMTests/vmPerformance"]
#[test_with = "jsontests::util::run_test"]
#[bench_with = "jsontests::util::run_bench"]
#[criterion_config = "criterion_cfg"]
struct Performance;

pub fn criterion_cfg() -> Criterion {
    // Due to poor SputnikVM performance, there's no chance to get a lot of measurements
    // and higher threshold is needed
    Criterion::default()
        .sample_size(2)
        .measurement_time(Duration::from_secs(10))
        .noise_threshold(0.07)
}
<commit_msg>fix missing dependency in benches<commit_after>#![allow(non_snake_case)]

#[macro_use]
extern crate jsontests_derive;
extern crate jsontests;
extern crate sputnikvm;
#[macro_use]
extern crate criterion;

use criterion::Criterion;
use std::time::Duration;

#[derive(JsonTests)]
#[directory = "jsontests/res/files/eth/VMTests/vmPerformance"]
#[test_with = "jsontests::util::run_test"]
#[bench_with = "jsontests::util::run_bench"]
#[criterion_config = "criterion_cfg"]
struct Performance;

pub fn criterion_cfg() -> Criterion {
    // Due to poor SputnikVM performance, there's no chance to get a lot of measurements
    // and higher threshold is needed
    Criterion::default()
        .sample_size(2)
        .measurement_time(Duration::from_secs(10))
        .noise_threshold(0.07)
}
<|endoftext|>


<commit_before>// Zinc, the bare metal stack for rust.
// Copyright 2014 Vladimir "farcaller" Pouzanov <farcaller@gmail.com>
//
// 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.

use syntax::ast;
use syntax::codemap::DUMMY_SP;
use syntax::ext::base::ExtCtxt;
use syntax::ext::build::AstBuilder;
use syntax::parse::token::{InternedString, intern_and_get_ident};
use syntax::ptr::P;

static TAG: &'static str = "__zinc_task_ty_params";

pub trait ToTyHash {
  fn to_tyhash(&self) -> String;
}

impl ToTyHash for String {
  fn to_tyhash(&self) -> String {
    format!("Ty{:X}", ::std::hash::hash(self))
  }
}

/// Sets ty_params for named task.
///
/// Arguments:
///   task: task name. This function must be called exactly once per task
///   args: a vector of type parameters
pub fn set_ty_params_for_task(cx: &mut ExtCtxt, task: &str, args: Vec<String>) {
  let ty_params = args.iter().map(|arg| {
    cx.meta_word(DUMMY_SP, intern_and_get_ident(arg.as_slice()))
  }).collect();
  let newmi = cx.meta_list(DUMMY_SP, intern_and_get_ident(task), ty_params);

  let mut tasks = get_tasks(cx);
  tasks.push(newmi);

  set_tasks(cx, tasks);
}

/// Returns a vector of type parameters for task.
pub fn get_ty_params_for_task(cx: &ExtCtxt, task: &str) -> Vec<String> {
  get_task(&get_tasks(cx), task)
}

/// Inserts or replaces tasks vector
fn set_tasks(cx: &mut ExtCtxt, tasks: Vec<P<ast::MetaItem>>) {
  let mut vec_clone = cx.cfg();
  let maybe_pos = vec_clone.iter().position(|i| {
    match i.node {
      ast::MetaList(ref k, _) if k.get() == TAG => true,
      _ => false,
    }
  });

  if maybe_pos.is_some() {
    vec_clone.remove(maybe_pos.unwrap());
  }
  vec_clone.push(cx.meta_list(DUMMY_SP, InternedString::new(TAG), tasks));

  cx.cfg = vec_clone;
}

/// Returns a vector of MetaLists where each MetaList corresponds to one task.
fn get_tasks(cx: &ExtCtxt) -> Vec<P<ast::MetaItem>> {
  for i in cx.cfg.iter() {
    match i.node {
      ast::MetaList(ref k, ref v) if k.get() == TAG => return v.clone(),
      _ => (),
    }
  };
  vec!()
}

/// Returns a vector of type parameters for named task.
fn get_task(tasks: &Vec<P<ast::MetaItem>>, task: &str) -> Vec<String> {
  let mut ty_params = vec!();
  for mi in tasks.iter() {
    match mi.node {
      ast::MetaList(ref k, ref v) if k.get() == task => {
        for submi in v.iter() {
          match submi.node {
            ast::MetaWord(ref w) => ty_params.push(w.get().to_string()),
            _ => panic!("unexpected node type"),
          }
        }
        break;
      },
      _ => (),
    }
  }
  ty_params
}

// pub fn get_ty_params_for_task(cx: &ExtCtxt, task: &str) -> Option<Vec<String>> {
//   get_task(cx, task).and_then(|ma| Some(ma.extra_ty_params.clone()))
// }

// fn get_task(cx: &ExtCtxt, task: &str) -> Option<MetaArgs> {
//   get_args(cx).and_then(|args| {
//     for a in args.iter() {
//       if a.task_name.as_slice() == task {
//         return Some(a.clone());
//       }
//     }
//     None
//   })
// }

// fn get_args(cx: &ExtCtxt) -> Option<Vec<MetaArgs>> {
//   cx.cfg.iter().find(|i| {
//     match i.node {
//       ast::MetaList(ref k, _) => k.get() == TAG,
//       _ => false,
//     }
//   }).and_then(|i| match i.node {
//     ast::MetaList(_, ref v) => Some(meta_item_to_meta_args(v)),
//     _ => panic!(),
//   })
// }

// fn meta_item_to_meta_args(mi: &Vec<P<ast::MetaItem>>) -> Vec<MetaArgs> {
//   let mut args = vec!();
//   for i in mi.iter() {
//     match i.node {
//       ast::MetaWord(ref istr) => {
//         let s = istr.get();

//         args.push(json::decode(s).unwrap());
//       },
//       _ => panic!(),
//     }
//   }

//   args
// }

// fn meta_args_to_meta_item(name: String, args: Vec<String>) -> P<ast::MetaItem> {
//   let ma = MetaArgs {
//     task_name: name,
//     extra_ty_params: args,
//   };
//   let enc = json::encode(&ma);
//   let istr = intern_and_get_ident(enc.as_slice());
//   box(GC) respan(DUMMY_SP, ast::MetaWord(istr))
// }
<commit_msg>hash::hash fix<commit_after>// Zinc, the bare metal stack for rust.
// Copyright 2014 Vladimir "farcaller" Pouzanov <farcaller@gmail.com>
//
// 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.

use syntax::ast;
use syntax::codemap::DUMMY_SP;
use syntax::ext::base::ExtCtxt;
use syntax::ext::build::AstBuilder;
use syntax::parse::token::{InternedString, intern_and_get_ident};
use syntax::ptr::P;
use std::hash::{hash, Hash, SipHasher};

static TAG: &'static str = "__zinc_task_ty_params";

pub trait ToTyHash {
  fn to_tyhash(&self) -> String;
}

impl ToTyHash for String {
  fn to_tyhash(&self) -> String {
    let h: u64 = hash::<_, SipHasher>(&self);
    format!("Ty{:X}", h)
  }
}

/// Sets ty_params for named task.
///
/// Arguments:
///   task: task name. This function must be called exactly once per task
///   args: a vector of type parameters
pub fn set_ty_params_for_task(cx: &mut ExtCtxt, task: &str, args: Vec<String>) {
  let ty_params = args.iter().map(|arg| {
    cx.meta_word(DUMMY_SP, intern_and_get_ident(arg.as_slice()))
  }).collect();
  let newmi = cx.meta_list(DUMMY_SP, intern_and_get_ident(task), ty_params);

  let mut tasks = get_tasks(cx);
  tasks.push(newmi);

  set_tasks(cx, tasks);
}

/// Returns a vector of type parameters for task.
pub fn get_ty_params_for_task(cx: &ExtCtxt, task: &str) -> Vec<String> {
  get_task(&get_tasks(cx), task)
}

/// Inserts or replaces tasks vector
fn set_tasks(cx: &mut ExtCtxt, tasks: Vec<P<ast::MetaItem>>) {
  let mut vec_clone = cx.cfg();
  let maybe_pos = vec_clone.iter().position(|i| {
    match i.node {
      ast::MetaList(ref k, _) if k.get() == TAG => true,
      _ => false,
    }
  });

  if maybe_pos.is_some() {
    vec_clone.remove(maybe_pos.unwrap());
  }
  vec_clone.push(cx.meta_list(DUMMY_SP, InternedString::new(TAG), tasks));

  cx.cfg = vec_clone;
}

/// Returns a vector of MetaLists where each MetaList corresponds to one task.
fn get_tasks(cx: &ExtCtxt) -> Vec<P<ast::MetaItem>> {
  for i in cx.cfg.iter() {
    match i.node {
      ast::MetaList(ref k, ref v) if k.get() == TAG => return v.clone(),
      _ => (),
    }
  };
  vec!()
}

/// Returns a vector of type parameters for named task.
fn get_task(tasks: &Vec<P<ast::MetaItem>>, task: &str) -> Vec<String> {
  let mut ty_params = vec!();
  for mi in tasks.iter() {
    match mi.node {
      ast::MetaList(ref k, ref v) if k.get() == task => {
        for submi in v.iter() {
          match submi.node {
            ast::MetaWord(ref w) => ty_params.push(w.get().to_string()),
            _ => panic!("unexpected node type"),
          }
        }
        break;
      },
      _ => (),
    }
  }
  ty_params
}

// pub fn get_ty_params_for_task(cx: &ExtCtxt, task: &str) -> Option<Vec<String>> {
//   get_task(cx, task).and_then(|ma| Some(ma.extra_ty_params.clone()))
// }

// fn get_task(cx: &ExtCtxt, task: &str) -> Option<MetaArgs> {
//   get_args(cx).and_then(|args| {
//     for a in args.iter() {
//       if a.task_name.as_slice() == task {
//         return Some(a.clone());
//       }
//     }
//     None
//   })
// }

// fn get_args(cx: &ExtCtxt) -> Option<Vec<MetaArgs>> {
//   cx.cfg.iter().find(|i| {
//     match i.node {
//       ast::MetaList(ref k, _) => k.get() == TAG,
//       _ => false,
//     }
//   }).and_then(|i| match i.node {
//     ast::MetaList(_, ref v) => Some(meta_item_to_meta_args(v)),
//     _ => panic!(),
//   })
// }

// fn meta_item_to_meta_args(mi: &Vec<P<ast::MetaItem>>) -> Vec<MetaArgs> {
//   let mut args = vec!();
//   for i in mi.iter() {
//     match i.node {
//       ast::MetaWord(ref istr) => {
//         let s = istr.get();

//         args.push(json::decode(s).unwrap());
//       },
//       _ => panic!(),
//     }
//   }

//   args
// }

// fn meta_args_to_meta_item(name: String, args: Vec<String>) -> P<ast::MetaItem> {
//   let ma = MetaArgs {
//     task_name: name,
//     extra_ty_params: args,
//   };
//   let enc = json::encode(&ma);
//   let istr = intern_and_get_ident(enc.as_slice());
//   box(GC) respan(DUMMY_SP, ast::MetaWord(istr))
// }
<|endoftext|>


<commit_before>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;
use std::collections::HashMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        let filename = Path::new(&ch.path).with_extension("html");

                        // Do several kinds of post-processing
                        let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_anchor_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_code_blocks(rendered);
                        let rendered = add_playpen_pre(rendered);

                        // Write to file
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));

        // do several kinds of post-processing
        let rendered = build_header_links(rendered, "print.html");
        let rendered = fix_anchor_links(rendered, "print.html");
        let rendered = fix_code_blocks(rendered);
        let rendered = add_playpen_pre(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    // Add google analytics tag
    if let Some(ref ga) = book.google_analytics {
        data.insert("google_analytics".to_owned(), json!(ga));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
    let mut id_counter = HashMap::new();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        let id_count = *id_counter.get(&id).unwrap_or(&0);
        id_counter.insert(id.clone(), id_count + 1);

        let id = if id_count > 0 {
            format!("{}-{}", id, id_count)
        } else {
            id
        };

        format!("<h{level}>{text}</h{level}>",
            level=level, id=id, text=text, filename=filename)
    }).into_owned()
}

// anchors to the same page (href="#anchor") do not work because of
// <base href="../"> pointing to the root folder. This function *fixes*
// that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r##"]+)href="#([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let anchor = &caps[2];
        let after = &caps[3];

        format!("",
            before=before, filename=filename, anchor=anchor, after=after)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}

fn add_playpen_pre(html: String) -> String {
    let regex = Regex::new(r##"((?s)]?class="([^"]+)".*?>(.*?))"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let text = &caps[1];
        let classes = &caps[2];
        let code = &caps[3];

        if classes.contains("language-rust") && !classes.contains("ignore") {
            // wrap the contents in an external pre block

            if text.contains("fn main") {
                format!("
{}
", text)
            } else {
                // we need to inject our own main
                let (attrs, code) = partition_source(code);
                format!("
# #![allow(unused_variables)]
{}#fn main() {{
{}
#}}
", classes, attrs, code)
            }
        } else {
            // not language-rust, so no-op
            format!("{}", text)
        }
    }).into_owned()
}

fn partition_source(s: &str) -> (String, String) {
    let mut after_header = false;
    let mut before = String::new();
    let mut after = String::new();

    for line in s.lines() {
        let trimline = line.trim();
        let header = trimline.chars().all(|c| c.is_whitespace()) ||
            trimline.starts_with("#![");
        if !header || after_header {
            after_header = true;
            after.push_str(line);
            after.push_str("\n");
        } else {
            before.push_str(line);
            before.push_str("\n");
        }
    }

    (before, after)
}<commit_msg>Do not add playpen boilerplate quick_main! is used<commit_after>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;
use std::collections::HashMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        let filename = Path::new(&ch.path).with_extension("html");

                        // Do several kinds of post-processing
                        let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_anchor_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_code_blocks(rendered);
                        let rendered = add_playpen_pre(rendered);

                        // Write to file
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));

        // do several kinds of post-processing
        let rendered = build_header_links(rendered, "print.html");
        let rendered = fix_anchor_links(rendered, "print.html");
        let rendered = fix_code_blocks(rendered);
        let rendered = add_playpen_pre(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    // Add google analytics tag
    if let Some(ref ga) = book.google_analytics {
        data.insert("google_analytics".to_owned(), json!(ga));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
    let mut id_counter = HashMap::new();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        let id_count = *id_counter.get(&id).unwrap_or(&0);
        id_counter.insert(id.clone(), id_count + 1);

        let id = if id_count > 0 {
            format!("{}-{}", id, id_count)
        } else {
            id
        };

        format!("<h{level}>{text}</h{level}>",
            level=level, id=id, text=text, filename=filename)
    }).into_owned()
}

// anchors to the same page (href="#anchor") do not work because of
// <base href="../"> pointing to the root folder. This function *fixes*
// that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r##"]+)href="#([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let anchor = &caps[2];
        let after = &caps[3];

        format!("",
            before=before, filename=filename, anchor=anchor, after=after)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}

fn add_playpen_pre(html: String) -> String {
    let regex = Regex::new(r##"((?s)]?class="([^"]+)".*?>(.*?))"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let text = &caps[1];
        let classes = &caps[2];
        let code = &caps[3];

        if classes.contains("language-rust") && !classes.contains("ignore") {
            // wrap the contents in an external pre block

            if text.contains("fn main") || text.contains("quick_main!") {
                format!("
{}
", text)
            } else {
                // we need to inject our own main
                let (attrs, code) = partition_source(code);
                format!("
# #![allow(unused_variables)]
{}#fn main() {{
{}
#}}
", classes, attrs, code)
            }
        } else {
            // not language-rust, so no-op
            format!("{}", text)
        }
    }).into_owned()
}

fn partition_source(s: &str) -> (String, String) {
    let mut after_header = false;
    let mut before = String::new();
    let mut after = String::new();

    for line in s.lines() {
        let trimline = line.trim();
        let header = trimline.chars().all(|c| c.is_whitespace()) ||
            trimline.starts_with("#![");
        if !header || after_header {
            after_header = true;
            after.push_str(line);
            after.push_str("\n");
        } else {
            before.push_str(line);
            before.push_str("\n");
        }
    }

    (before, after)
}<|endoftext|>