<commit_before>// Copyright 2016 metal-rs developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

extern crate objc;

use cocoa::{appkit::NSView, base::id as cocoa_id};

use metal::*;
use objc::{rc::autoreleasepool, runtime::YES};
use std::mem;
use winit::platform::macos::WindowExtMacOS;

use winit::{
    event::{Event, WindowEvent},
    event_loop::ControlFlow,
};

#[repr(C)]
struct Rect {
    pub x: f32,
    pub y: f32,
    pub w: f32,
    pub h: f32,
}

#[repr(C)]
struct Color {
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
}

#[repr(C)]
struct ClearRect {
    pub rect: Rect,
    pub color: Color,
}

fn prepare_pipeline_state<'a>(
    device: &DeviceRef,
    library: &LibraryRef,
    vertex_shader: &str,
    fragment_shader: &str,
) -> RenderPipelineState {
    let vert = library.get_function(vertex_shader, None).unwrap();
    let frag = library.get_function(fragment_shader, None).unwrap();

    let pipeline_state_descriptor = RenderPipelineDescriptor::new();
    pipeline_state_descriptor.set_vertex_function(Some(&vert));
    pipeline_state_descriptor.set_fragment_function(Some(&frag));
    let attachment = pipeline_state_descriptor
        .color_attachments()
        .object_at(0)
        .unwrap();
    attachment.set_pixel_format(MTLPixelFormat::BGRA8Unorm);

    attachment.set_blending_enabled(true);
    attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
    attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
    attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
    attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::SourceAlpha);
    attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
    attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);

    device
        .new_render_pipeline_state(&pipeline_state_descriptor)
        .unwrap()
}

fn prepare_render_pass_descriptor(descriptor: &RenderPassDescriptorRef, texture: &TextureRef) {
    //descriptor.color_attachments().set_object_at(0, MTLRenderPassColorAttachmentDescriptor::alloc());
    //let color_attachment: MTLRenderPassColorAttachmentDescriptor = unsafe { msg_send![descriptor.color_attachments().0, _descriptorAtIndex:0] };//descriptor.color_attachments().object_at(0);
    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();

    color_attachment.set_texture(Some(texture));
    color_attachment.set_load_action(MTLLoadAction::Clear);
    color_attachment.set_clear_color(MTLClearColor::new(0.2, 0.2, 0.25, 1.0));
    color_attachment.set_store_action(MTLStoreAction::Store);
}

fn main() {
    let events_loop = winit::event_loop::EventLoop::new();
    let size = winit::dpi::LogicalSize::new(800, 600);

    let window = winit::window::WindowBuilder::new()
        .with_inner_size(size)
        .with_title("Metal Window Example".to_string())
        .build(&events_loop)
        .unwrap();

    let device = Device::system_default().expect("no device found");

    let layer = MetalLayer::new();
    layer.set_device(&device);
    layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
    layer.set_presents_with_transaction(false);

    unsafe {
        let view = window.ns_view() as cocoa_id;
        view.setWantsLayer(YES);
        view.setLayer(mem::transmute(layer.as_ref()));
    }

    let draw_size = window.inner_size();
    layer.set_drawable_size(CGSize::new(draw_size.width as f64, draw_size.height as f64));

    let library_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("examples/window/shaders.metallib");

    let library = device.new_library_with_file(library_path).unwrap();
    let triangle_pipeline_state =
        prepare_pipeline_state(&device, &library, "triangle_vertex", "triangle_fragment");
    let clear_rect_pipeline_state = prepare_pipeline_state(
        &device,
        &library,
        "clear_rect_vertex",
        "clear_rect_fragment",
    );

    let command_queue = device.new_command_queue();
    //let nc: () = msg_send![command_queue.0, setExecutionEnabled:true];

    let vbuf = {
        let vertex_data = [
            0.0f32, 0.5, 1.0, 0.0, 0.0, -0.5, -0.5, 0.0, 1.0, 0.0, 0.5, 0.5, 0.0, 0.0, 1.0,
        ];

        device.new_buffer_with_data(
            vertex_data.as_ptr() as *const _,
            (vertex_data.len() * mem::size_of::<f32>()) as u64,
            MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged,
        )
    };

    let mut r = 0.0f32;

    let clear_rect = vec![ClearRect {
        rect: Rect {
            x: -1.0,
            y: -1.0,
            w: 2.0,
            h: 2.0,
        },
        color: Color {
            r: 0.5,
            g: 0.8,
            b: 0.5,
            a: 1.0,
        },
    }];

    let clear_rect_buffer = device.new_buffer_with_data(
        clear_rect.as_ptr() as *const _,
        mem::size_of::<ClearRect>() as u64,
        MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged,
    );

    events_loop.run(move |event, _, control_flow| {
        autoreleasepool(|| {
            *control_flow = ControlFlow::Poll;

            match event {
                Event::WindowEvent { event, .. } => match event {
                    WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
                    WindowEvent::Resized(size) => {
                        layer.set_drawable_size(CGSize::new(size.width as f64, size.height as f64));
                    }
                    _ => (),
                },
                Event::MainEventsCleared => {
                    window.request_redraw();
                }
                Event::RedrawRequested(_) => {
                    let p = vbuf.contents();
                    let vertex_data = [
                        0.0f32,
                        0.5,
                        1.0,
                        0.0,
                        0.0,
                        -0.5 + (r.cos() / 2. + 0.5),
                        -0.5,
                        0.0,
                        1.0,
                        0.0,
                        0.5 - (r.cos() / 2. + 0.5),
                        -0.5,
                        0.0,
                        0.0,
                        1.0,
                    ];

                    unsafe {
                        std::ptr::copy(
                            vertex_data.as_ptr(),
                            p as *mut f32,
                            (vertex_data.len() * mem::size_of::<f32>()) as usize,
                        );
                    }

                    vbuf.did_modify_range(crate::NSRange::new(
                        0 as u64,
                        (vertex_data.len() * mem::size_of::<f32>()) as u64,
                    ));

                    let drawable = match layer.next_drawable() {
                        Some(drawable) => drawable,
                        None => return,
                    };

                    let render_pass_descriptor = RenderPassDescriptor::new();

                    prepare_render_pass_descriptor(&render_pass_descriptor, drawable.texture());

                    let command_buffer = command_queue.new_command_buffer();
                    let encoder =
                        command_buffer.new_render_command_encoder(&render_pass_descriptor);

                    encoder.set_scissor_rect(MTLScissorRect {
                        x: 20,
                        y: 20,
                        width: 100,
                        height: 100,
                    });
                    encoder.set_render_pipeline_state(&clear_rect_pipeline_state);
                    encoder.set_vertex_buffer(0, Some(&clear_rect_buffer), 0);
                    encoder.draw_primitives_instanced(
                        metal::MTLPrimitiveType::TriangleStrip,
                        0,
                        4,
                        1,
                    );
                    encoder.set_scissor_rect(MTLScissorRect {
                        x: 0,
                        y: 0,
                        width: size.width as _,
                        height: size.height as _,
                    });

                    encoder.set_render_pipeline_state(&triangle_pipeline_state);
                    encoder.set_vertex_buffer(0, Some(&vbuf), 0);
                    encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 3);
                    encoder.end_encoding();

                    command_buffer.present_drawable(&drawable);
                    command_buffer.commit();

                    r += 0.01f32;
                }
                _ => {}
            }
        });
    });
}
<commit_msg>window example: Fix rendering on hdpi screens (#195)<commit_after>// Copyright 2016 metal-rs developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

extern crate objc;

use cocoa::{appkit::NSView, base::id as cocoa_id};

use metal::*;
use objc::{rc::autoreleasepool, runtime::YES};
use std::mem;
use winit::platform::macos::WindowExtMacOS;

use winit::{
    event::{Event, WindowEvent},
    event_loop::ControlFlow,
};

#[repr(C)]
struct Rect {
    pub x: f32,
    pub y: f32,
    pub w: f32,
    pub h: f32,
}

#[repr(C)]
struct Color {
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
}

#[repr(C)]
struct ClearRect {
    pub rect: Rect,
    pub color: Color,
}

fn prepare_pipeline_state<'a>(
    device: &DeviceRef,
    library: &LibraryRef,
    vertex_shader: &str,
    fragment_shader: &str,
) -> RenderPipelineState {
    let vert = library.get_function(vertex_shader, None).unwrap();
    let frag = library.get_function(fragment_shader, None).unwrap();

    let pipeline_state_descriptor = RenderPipelineDescriptor::new();
    pipeline_state_descriptor.set_vertex_function(Some(&vert));
    pipeline_state_descriptor.set_fragment_function(Some(&frag));
    let attachment = pipeline_state_descriptor
        .color_attachments()
        .object_at(0)
        .unwrap();
    attachment.set_pixel_format(MTLPixelFormat::BGRA8Unorm);

    attachment.set_blending_enabled(true);
    attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
    attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
    attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
    attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::SourceAlpha);
    attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
    attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);

    device
        .new_render_pipeline_state(&pipeline_state_descriptor)
        .unwrap()
}

fn prepare_render_pass_descriptor(descriptor: &RenderPassDescriptorRef, texture: &TextureRef) {
    //descriptor.color_attachments().set_object_at(0, MTLRenderPassColorAttachmentDescriptor::alloc());
    //let color_attachment: MTLRenderPassColorAttachmentDescriptor = unsafe { msg_send![descriptor.color_attachments().0, _descriptorAtIndex:0] };//descriptor.color_attachments().object_at(0);
    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();

    color_attachment.set_texture(Some(texture));
    color_attachment.set_load_action(MTLLoadAction::Clear);
    color_attachment.set_clear_color(MTLClearColor::new(0.2, 0.2, 0.25, 1.0));
    color_attachment.set_store_action(MTLStoreAction::Store);
}

fn main() {
    let events_loop = winit::event_loop::EventLoop::new();
    let size = winit::dpi::LogicalSize::new(800, 600);

    let window = winit::window::WindowBuilder::new()
        .with_inner_size(size)
        .with_title("Metal Window Example".to_string())
        .build(&events_loop)
        .unwrap();

    let device = Device::system_default().expect("no device found");

    let layer = MetalLayer::new();
    layer.set_device(&device);
    layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
    layer.set_presents_with_transaction(false);

    unsafe {
        let view = window.ns_view() as cocoa_id;
        view.setWantsLayer(YES);
        view.setLayer(mem::transmute(layer.as_ref()));
    }

    let draw_size = window.inner_size();
    layer.set_drawable_size(CGSize::new(draw_size.width as f64, draw_size.height as f64));

    let library_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("examples/window/shaders.metallib");

    let library = device.new_library_with_file(library_path).unwrap();
    let triangle_pipeline_state =
        prepare_pipeline_state(&device, &library, "triangle_vertex", "triangle_fragment");
    let clear_rect_pipeline_state = prepare_pipeline_state(
        &device,
        &library,
        "clear_rect_vertex",
        "clear_rect_fragment",
    );

    let command_queue = device.new_command_queue();
    //let nc: () = msg_send![command_queue.0, setExecutionEnabled:true];

    let vbuf = {
        let vertex_data = [
            0.0f32, 0.5, 1.0, 0.0, 0.0, -0.5, -0.5, 0.0, 1.0, 0.0, 0.5, 0.5, 0.0, 0.0, 1.0,
        ];

        device.new_buffer_with_data(
            vertex_data.as_ptr() as *const _,
            (vertex_data.len() * mem::size_of::<f32>()) as u64,
            MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged,
        )
    };

    let mut r = 0.0f32;

    let clear_rect = vec![ClearRect {
        rect: Rect {
            x: -1.0,
            y: -1.0,
            w: 2.0,
            h: 2.0,
        },
        color: Color {
            r: 0.5,
            g: 0.8,
            b: 0.5,
            a: 1.0,
        },
    }];

    let clear_rect_buffer = device.new_buffer_with_data(
        clear_rect.as_ptr() as *const _,
        mem::size_of::<ClearRect>() as u64,
        MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged,
    );

    events_loop.run(move |event, _, control_flow| {
        autoreleasepool(|| {
            *control_flow = ControlFlow::Poll;

            match event {
                Event::WindowEvent { event, .. } => match event {
                    WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
                    WindowEvent::Resized(size) => {
                        layer.set_drawable_size(CGSize::new(size.width as f64, size.height as f64));
                    }
                    _ => (),
                },
                Event::MainEventsCleared => {
                    window.request_redraw();
                }
                Event::RedrawRequested(_) => {
                    let p = vbuf.contents();
                    let vertex_data = [
                        0.0f32,
                        0.5,
                        1.0,
                        0.0,
                        0.0,
                        -0.5 + (r.cos() / 2. + 0.5),
                        -0.5,
                        0.0,
                        1.0,
                        0.0,
                        0.5 - (r.cos() / 2. + 0.5),
                        -0.5,
                        0.0,
                        0.0,
                        1.0,
                    ];

                    unsafe {
                        std::ptr::copy(
                            vertex_data.as_ptr(),
                            p as *mut f32,
                            (vertex_data.len() * mem::size_of::<f32>()) as usize,
                        );
                    }

                    vbuf.did_modify_range(crate::NSRange::new(
                        0 as u64,
                        (vertex_data.len() * mem::size_of::<f32>()) as u64,
                    ));

                    let drawable = match layer.next_drawable() {
                        Some(drawable) => drawable,
                        None => return,
                    };

                    let render_pass_descriptor = RenderPassDescriptor::new();

                    prepare_render_pass_descriptor(&render_pass_descriptor, drawable.texture());

                    let command_buffer = command_queue.new_command_buffer();
                    let encoder =
                        command_buffer.new_render_command_encoder(&render_pass_descriptor);

                    encoder.set_scissor_rect(MTLScissorRect {
                        x: 20,
                        y: 20,
                        width: 100,
                        height: 100,
                    });
                    encoder.set_render_pipeline_state(&clear_rect_pipeline_state);
                    encoder.set_vertex_buffer(0, Some(&clear_rect_buffer), 0);
                    encoder.draw_primitives_instanced(
                        metal::MTLPrimitiveType::TriangleStrip,
                        0,
                        4,
                        1,
                    );
                    let physical_size = window.inner_size();
                    encoder.set_scissor_rect(MTLScissorRect {
                        x: 0,
                        y: 0,
                        width: physical_size.width as _,
                        height: physical_size.height as _,
                    });

                    encoder.set_render_pipeline_state(&triangle_pipeline_state);
                    encoder.set_vertex_buffer(0, Some(&vbuf), 0);
                    encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 3);
                    encoder.end_encoding();

                    command_buffer.present_drawable(&drawable);
                    command_buffer.commit();

                    r += 0.01f32;
                }
                _ => {}
            }
        });
    });
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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 std::env;
use std::ffi::OsString;

use common::ui::UI;
use hcore::crypto::CACHE_KEY_PATH_ENV_VAR;
use hcore::env as henv;
use hcore::fs::CACHE_KEY_PATH;
use hcore::os::users;

use config;
use error::Result;

const STUDIO_CMD: &'static str = "hab-studio";
const STUDIO_CMD_ENVVAR: &'static str = "HAB_STUDIO_BINARY";
const STUDIO_PACKAGE_IDENT: &'static str = "core/hab-studio";

pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
    inner::rerun_with_sudo_if_needed(ui)?;

    // If the `$HAB_ORIGIN` environment variable is not present, then see if a default is set in
    // the CLI config. If so, set it as the `$HAB_ORIGIN` environment variable for the `hab-studio`
    // or `docker` execv call.
    if henv::var("HAB_ORIGIN").is_err() {
        let config = config::load_with_sudo_user()?;
        if let Some(default_origin) = config.origin {
            debug!("Setting default origin {} via CLI config", &default_origin);
            env::set_var("HAB_ORIGIN", default_origin);
        }
    }

    // If the `$HAB_CACHE_KEY_PATH` environment variable is not present, check if we are running
    // under a `sudo` invocation. If so, determine the non-root user that issued the command in
    // order to set their key cache location in the environment variable. This is done so that the
    // `hab-studio` command will find the correct key cache or so that the correct directory will
    // be volume mounted when used with Docker.
    if henv::var(CACHE_KEY_PATH_ENV_VAR).is_err() {
        if let Some(sudo_user) = henv::sudo_user() {
            if let Some(home) = users::get_home_for_user(&sudo_user) {
                let cache_key_path = home.join(format!(".{}", CACHE_KEY_PATH));
                debug!(
                    "Setting cache_key_path for SUDO_USER={} to: {}",
                    &sudo_user,
                    cache_key_path.display()
                );
                env::set_var(CACHE_KEY_PATH_ENV_VAR, cache_key_path);
                // Prevent any inner `hab` invocations from triggering similar logic: we will be
                // operating in the context `hab-studio` which is running with root like privileges.
                env::remove_var("SUDO_USER");
            }
        }
    }

    inner::start(ui, args)
}

#[cfg(target_os = "linux")]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{am_i_root, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;
    use VERSION;

    const SUDO_CMD: &'static str = "sudo";

    pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(super::STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let version: Vec<&str> = VERSION.split("/").collect();
                let ident = PackageIdent::from_str(
                    &format!("{}/{}", super::STUDIO_PACKAGE_IDENT, version[0]),
                )?;
                exec::command_from_min_pkg(
                    ui,
                    super::STUDIO_CMD,
                    &ident,
                    &default_cache_key_path(None),
                    0,
                )?
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            Ok(process::become_command(cmd, args)?)
        } else {
            Err(Error::ExecCommandNotFound(command))
        }
    }

    pub fn rerun_with_sudo_if_needed(ui: &mut UI) -> Result<()> {
        // If I have root permissions, early return, we are done.
        if am_i_root() {
            return Ok(());
        }

        // Otherwise we will try to re-run this program using `sudo`
        match find_command(SUDO_CMD) {
            Some(sudo_prog) => {
                let mut args: Vec<OsString> = vec![
                    "-p".into(),
                    "[sudo hab-studio] password for %u: ".into(),
                    "-E".into(),
                ];
                args.append(&mut env::args_os().collect());
                Ok(process::become_command(sudo_prog, args)?)
            }
            None => {
                ui.warn(format!(
                    "Could not find the `{}' command, is it in your PATH?",
                    SUDO_CMD
                ))?;
                ui.warn(
                    "Running Habitat Studio requires root or administrator privileges. \
                              Please retry this command as a super user or use a \
                              privilege-granting facility such as sudo.",
                )?;
                ui.br()?;
                Err(Error::RootRequired)
            }
        }
    }
}

#[cfg(not(target_os = "linux"))]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::process::{Command, Stdio};
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{CACHE_KEY_PATH, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;
    use VERSION;

    const DOCKER_CMD: &'static str = "docker";
    const DOCKER_CMD_ENVVAR: &'static str = "HAB_DOCKER_BINARY";
    const DOCKER_IMAGE: &'static str = "habitat-docker-registry.bintray.io/studio";
    const DOCKER_IMAGE_ENVVAR: &'static str = "HAB_DOCKER_STUDIO_IMAGE";
    const DOCKER_OPTS: &'static str = "HAB_DOCKER_OPTS";

    pub fn start(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        if is_windows_studio(&args) {
            start_windows_studio(_ui, args)
        } else {
            start_docker_studio(_ui, args)
        }
    }

    pub fn start_windows_studio(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(super::STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let version: Vec<&str> = VERSION.split("/").collect();
                let ident = PackageIdent::from_str(
                    &format!("{}/{}", super::STUDIO_PACKAGE_IDENT, version[0]),
                )?;
                exec::command_from_min_pkg(
                    ui,
                    super::STUDIO_CMD,
                    &ident,
                    &default_cache_key_path(None),
                    0,
                )?
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            process::become_command(cmd, args)?;
        } else {
            return Err(Error::ExecCommandNotFound(command));
        }
        Ok(())
    }

    pub fn start_docker_studio(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let docker = henv::var(DOCKER_CMD_ENVVAR).unwrap_or(DOCKER_CMD.to_string());

        let cmd = match find_command(&docker) {
            Some(cmd) => cmd,
            None => return Err(Error::ExecCommandNotFound(docker.into())),
        };

        let output = Command::new(&cmd)
            .arg("images")
            .arg(&image_identifier())
            .arg("-q")
            .output()
            .expect("docker failed to start");

        let stdout = String::from_utf8(output.stdout).unwrap();
        if stdout.is_empty() {
            debug!("Failed to find studio image locally.");

            let child = Command::new(&cmd)
                .arg("pull")
                .arg(&image_identifier())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .spawn()
                .expect("docker failed to start");

            let output = child.wait_with_output().expect("failed to wait on child");

            if output.status.success() {
                debug!("Docker image is reachable. Proceeding with launching docker.");
            } else {
                debug!(
                    "Docker image is unreachable. Exit code = {:?}",
                    output.status
                );

                let err_output = String::from_utf8(output.stderr).unwrap();

                if err_output.contains("image") && err_output.contains("not found") {
                    return Err(Error::DockerImageNotFound(image_identifier().to_string()));
                } else if err_output.contains("Cannot connect to the Docker daemon") {
                    return Err(Error::DockerDaemonDown);
                } else {
                    return Err(Error::DockerNetworkDown(image_identifier().to_string()));
                }
            }
        } else {
            debug!("Found studio image locally.");
            debug!("Local image id: {}", stdout);
        }

        // We need to ensure that filesystem sharing has been enabled, otherwise the user will
        // be greeted with a horrible error message that's difficult to make sense of. To
        // mitigate this, we check the studio version. This will cause Docker to go through the
        // mounting steps, so we can watch stderr for failure, but has the advantage of not
        // requiring a TTY.

        let current_dir = format!("{}:/src", env::current_dir().unwrap().to_string_lossy());
        let key_cache_path = format!(
            "{}:/{}",
            default_cache_key_path(None).to_string_lossy(),
            CACHE_KEY_PATH
        );
        let version_output = Command::new(&cmd)
            .arg("run")
            .arg("--rm")
            .arg("--privileged")
            .arg("--volume")
            .arg(&key_cache_path)
            .arg("--volume")
            .arg(&current_dir)
            .arg(image_identifier())
            .arg("-V")
            .output()
            .expect("docker failed to start");

        let stderr = String::from_utf8(version_output.stderr).unwrap();
        if !stderr.is_empty() &&
            (stderr.as_str().contains("Mounts denied") ||
                 stderr.as_str().contains("drive is not shared"))
        {
            return Err(Error::DockerFileSharingNotEnabled);
        }

        // If we make it here, filesystem sharing has been setup correctly, so let's proceed like
        // normal.

        let mut cmd_args: Vec<OsString> = vec![
            "run".into(),
            "--rm".into(),
            "--tty".into(),
            "--interactive".into(),
            "--privileged".into(),
        ];

        // All the args already placed in `cmd_args` are things that we don't want to insert again.
        // Later args such as `--env` will overwrite any options (potentially) set mistakenly here.
        if let Ok(opts) = henv::var(DOCKER_OPTS) {
            let opts = opts.split(" ")
                .map(|v| v.into())
                // Ensure we're not passing something like `--tty` again here.
                .filter(|v| !cmd_args.contains(v))
                .collect::<Vec<_>>();
            debug!("Docker opts originating from DOCKER_OPTS = {:?}", opts);
            cmd_args.extend_from_slice(opts.as_slice());
        }

        let env_vars = vec![
            "HAB_DEPOT_URL",
            "HAB_DEPOT_CHANNEL",
            "HAB_ORIGIN",
            "HAB_STUDIO_SUP",
            "HAB_UPDATE_STRATEGY_FREQUENCY_MS",
            "http_proxy",
            "https_proxy",
        ];
        for var in env_vars {
            if let Ok(val) = henv::var(var) {
                debug!(
                    "Propagating environment variable into container: {}={}",
                    var,
                    val
                );
                cmd_args.push("--env".into());
                cmd_args.push(format!("{}={}", var, val).into());
            }
        }

        cmd_args.push("--volume".into());
        cmd_args.push("/var/run/docker.sock:/var/run/docker.sock".into());
        cmd_args.push("--volume".into());
        cmd_args.push(key_cache_path.into());
        cmd_args.push("--volume".into());
        cmd_args.push(current_dir.into());

        cmd_args.push(image_identifier().into());
        cmd_args.extend_from_slice(args.as_slice());

        for var in vec!["http_proxy", "https_proxy"] {
            if let Ok(_) = henv::var(var) {
                debug!(
                    "Unsetting proxy environment variable '{}' before calling `{}'",
                    var,
                    docker
                );
                env::remove_var(var);
            }
        }

        debug!("Docker arguments = {:?}", cmd_args);
        Ok(process::become_command(cmd, cmd_args)?)
    }

    pub fn rerun_with_sudo_if_needed(_ui: &mut UI) -> Result<()> {
        // No sudo calls necessary here--we are calling `docker` commands instead
        Ok(())
    }

    /// Returns the Docker Studio image with tag for the desired version which corresponds to the
    /// same version (minus release) as this program.
    fn image_identifier() -> String {
        let version: Vec<&str> = VERSION.split("/").collect();
        henv::var(DOCKER_IMAGE_ENVVAR).unwrap_or(format!("{}:{}", DOCKER_IMAGE, version[0]))
    }

    fn is_windows_studio(args: &Vec<OsString>) -> bool {
        if cfg!(not(target_os = "windows")) {
            return false;
        }

        for arg in args.iter() {
            let str_arg = arg.to_string_lossy().to_lowercase();
            if str_arg == String::from("--windows") || str_arg == String::from("-w") {
                return true;
            }
        }

        return false;
    }

    #[cfg(test)]
    mod tests {
        use super::{image_identifier, DOCKER_IMAGE};
        use VERSION;

        #[test]
        fn retrieve_image_identifier() {
            assert_eq!(image_identifier(), format!("{}:{}", DOCKER_IMAGE, VERSION));
        }
    }
}
<commit_msg>[hab] Refactor Docker Studio setup code.<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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 std::env;
use std::ffi::OsString;

use common::ui::UI;
use hcore::crypto::CACHE_KEY_PATH_ENV_VAR;
use hcore::env as henv;
use hcore::fs::CACHE_KEY_PATH;
use hcore::os::users;

use config;
use error::Result;

const STUDIO_CMD: &'static str = "hab-studio";
const STUDIO_CMD_ENVVAR: &'static str = "HAB_STUDIO_BINARY";
const STUDIO_PACKAGE_IDENT: &'static str = "core/hab-studio";

pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
    inner::rerun_with_sudo_if_needed(ui)?;

    // If the `$HAB_ORIGIN` environment variable is not present, then see if a default is set in
    // the CLI config. If so, set it as the `$HAB_ORIGIN` environment variable for the `hab-studio`
    // or `docker` execv call.
    if henv::var("HAB_ORIGIN").is_err() {
        let config = config::load_with_sudo_user()?;
        if let Some(default_origin) = config.origin {
            debug!("Setting default origin {} via CLI config", &default_origin);
            env::set_var("HAB_ORIGIN", default_origin);
        }
    }

    // If the `$HAB_CACHE_KEY_PATH` environment variable is not present, check if we are running
    // under a `sudo` invocation. If so, determine the non-root user that issued the command in
    // order to set their key cache location in the environment variable. This is done so that the
    // `hab-studio` command will find the correct key cache or so that the correct directory will
    // be volume mounted when used with Docker.
    if henv::var(CACHE_KEY_PATH_ENV_VAR).is_err() {
        if let Some(sudo_user) = henv::sudo_user() {
            if let Some(home) = users::get_home_for_user(&sudo_user) {
                let cache_key_path = home.join(format!(".{}", CACHE_KEY_PATH));
                debug!(
                    "Setting cache_key_path for SUDO_USER={} to: {}",
                    &sudo_user,
                    cache_key_path.display()
                );
                env::set_var(CACHE_KEY_PATH_ENV_VAR, cache_key_path);
                // Prevent any inner `hab` invocations from triggering similar logic: we will be
                // operating in the context `hab-studio` which is running with root like privileges.
                env::remove_var("SUDO_USER");
            }
        }
    }

    inner::start(ui, args)
}

#[cfg(target_os = "linux")]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{am_i_root, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;
    use VERSION;

    const SUDO_CMD: &'static str = "sudo";

    pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(super::STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let version: Vec<&str> = VERSION.split("/").collect();
                let ident = PackageIdent::from_str(
                    &format!("{}/{}", super::STUDIO_PACKAGE_IDENT, version[0]),
                )?;
                exec::command_from_min_pkg(
                    ui,
                    super::STUDIO_CMD,
                    &ident,
                    &default_cache_key_path(None),
                    0,
                )?
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            Ok(process::become_command(cmd, args)?)
        } else {
            Err(Error::ExecCommandNotFound(command))
        }
    }

    pub fn rerun_with_sudo_if_needed(ui: &mut UI) -> Result<()> {
        // If I have root permissions, early return, we are done.
        if am_i_root() {
            return Ok(());
        }

        // Otherwise we will try to re-run this program using `sudo`
        match find_command(SUDO_CMD) {
            Some(sudo_prog) => {
                let mut args: Vec<OsString> = vec![
                    "-p".into(),
                    "[sudo hab-studio] password for %u: ".into(),
                    "-E".into(),
                ];
                args.append(&mut env::args_os().collect());
                Ok(process::become_command(sudo_prog, args)?)
            }
            None => {
                ui.warn(format!(
                    "Could not find the `{}' command, is it in your PATH?",
                    SUDO_CMD
                ))?;
                ui.warn(
                    "Running Habitat Studio requires root or administrator privileges. \
                              Please retry this command as a super user or use a \
                              privilege-granting facility such as sudo.",
                )?;
                ui.br()?;
                Err(Error::RootRequired)
            }
        }
    }
}

#[cfg(not(target_os = "linux"))]
mod inner {
    use std::env;
    use std::ffi::{OsStr, OsString};
    use std::path::{Path, PathBuf};
    use std::process::{Command, Stdio};
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{CACHE_KEY_PATH, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;
    use VERSION;

    const DOCKER_CMD: &'static str = "docker";
    const DOCKER_CMD_ENVVAR: &'static str = "HAB_DOCKER_BINARY";
    const DOCKER_IMAGE: &'static str = "habitat-docker-registry.bintray.io/studio";
    const DOCKER_IMAGE_ENVVAR: &'static str = "HAB_DOCKER_STUDIO_IMAGE";
    const DOCKER_OPTS_ENVVAR: &'static str = "HAB_DOCKER_OPTS";
    const DOCKER_SOCKET: &'static str = "/var/run/docker.sock";

    pub fn start(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        if is_windows_studio(&args) {
            start_windows_studio(_ui, args)
        } else {
            start_docker_studio(_ui, args)
        }
    }

    pub fn start_windows_studio(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(super::STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let version: Vec<&str> = VERSION.split("/").collect();
                let ident = PackageIdent::from_str(
                    &format!("{}/{}", super::STUDIO_PACKAGE_IDENT, version[0]),
                )?;
                exec::command_from_min_pkg(
                    ui,
                    super::STUDIO_CMD,
                    &ident,
                    &default_cache_key_path(None),
                    0,
                )?
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            process::become_command(cmd, args)?;
        } else {
            return Err(Error::ExecCommandNotFound(command));
        }
        Ok(())
    }

    pub fn start_docker_studio(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let docker_cmd = find_docker_cmd()?;

        if is_image_present(&docker_cmd) {
            debug!("Found Studio Docker image locally.");
        } else {
            debug!("Failed to find Studio Docker image locally.");
            pull_image(&docker_cmd)?;
        }

        let mut volumes = vec![
            format!("{}:/src", env::current_dir().unwrap().to_string_lossy()),
            format!(
                "{}:/{}",
                default_cache_key_path(None).to_string_lossy(),
                CACHE_KEY_PATH
            ),
        ];
        if Path::new(DOCKER_SOCKET).exists() {
            volumes.push(format!("{}:{}", DOCKER_SOCKET, DOCKER_SOCKET));
        }

        let env_vars = vec![
            "HAB_DEPOT_URL",
            "HAB_DEPOT_CHANNEL",
            "HAB_ORIGIN",
            "HAB_STUDIO_SUP",
            "HAB_UPDATE_STRATEGY_FREQUENCY_MS",
            "http_proxy",
            "https_proxy",
        ];

        check_mounts(&docker_cmd, volumes.iter())?;
        run_container(docker_cmd, args, volumes.iter(), env_vars.iter())
    }

    pub fn rerun_with_sudo_if_needed(_ui: &mut UI) -> Result<()> {
        // No sudo calls necessary here--we are calling `docker` commands instead
        Ok(())
    }

    fn find_docker_cmd() -> Result<PathBuf> {
        let docker_cmd = henv::var(DOCKER_CMD_ENVVAR).unwrap_or(DOCKER_CMD.to_string());

        match find_command(&docker_cmd) {
            Some(docker_abs_path) => Ok(docker_abs_path),
            None => Err(Error::ExecCommandNotFound(docker_cmd.into())),
        }
    }

    fn is_image_present(docker_cmd: &Path) -> bool {
        let mut cmd = Command::new(docker_cmd);
        cmd.arg("images").arg(&image_identifier()).arg("-q");
        debug!("Running command: {:?}", cmd);
        let result = cmd.output().expect("Docker command failed to spawn");

        !String::from_utf8_lossy(&result.stdout).as_ref().is_empty()
    }

    fn pull_image(docker_cmd: &Path) -> Result<()> {
        let image = image_identifier();
        let mut cmd = Command::new(docker_cmd);
        cmd.arg("pull")
            .arg(&image)
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit());
        debug!("Running command: {:?}", cmd);
        let result = cmd.spawn()
            .expect("Docker command failed to spawn")
            .wait_with_output()
            .expect("Failed to wait on child process");

        if result.status.success() {
            debug!("Docker image '{}' is present locally.", &image);
        } else {
            debug!(
                "Pulling Docker image '{}' failed with exit code: {:?}",
                &image,
                result.status
            );

            let err_output = String::from_utf8_lossy(&result.stderr);

            if err_output.contains("image") && err_output.contains("not found") {
                return Err(Error::DockerImageNotFound(image_identifier().to_string()));
            } else if err_output.contains("Cannot connect to the Docker daemon") {
                return Err(Error::DockerDaemonDown);
            } else {
                return Err(Error::DockerNetworkDown(image_identifier().to_string()));
            }
        }

        Ok(())
    }

    /// Checks whether or not the volume mounts are working.
    ///
    /// We need to ensure that filesystem sharing has been enabled, otherwise the user will be
    /// greeted with a horrible error message that's difficult to make sense of. To mitigate this,
    /// we check the studio version. This will cause Docker to go through the mounting steps, so we
    /// can watch stderr for failure, but has the advantage of not requiring a TTY.
    fn check_mounts<I, S>(docker_cmd: &Path, volumes: I) -> Result<()>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
    {
        let mut volume_args: Vec<OsString> = Vec::new();
        for vol in volumes {
            volume_args.push("--volume".into());
            volume_args.push(vol.as_ref().into());
        }

        let version_output = Command::new(docker_cmd)
            .arg("run")
            .arg("--rm")
            .arg("--privileged")
            .args(volume_args)
            .arg(image_identifier())
            .arg("-V")
            .output()
            .expect("docker failed to start");

        let stderr = String::from_utf8(version_output.stderr).unwrap();
        if !stderr.is_empty() &&
            (stderr.as_str().contains("Mounts denied") ||
                 stderr.as_str().contains("drive is not shared"))
        {
            return Err(Error::DockerFileSharingNotEnabled);
        }
        Ok(())
    }

    fn run_container<I, J, S, T>(
        docker_cmd: PathBuf,
        args: Vec<OsString>,
        volumes: I,
        env_vars: J,
    ) -> Result<()>
    where
        I: IntoIterator<Item = S>,
        J: IntoIterator<Item = T>,
        S: AsRef<OsStr>,
        T: AsRef<str>,
    {
        let mut cmd_args: Vec<OsString> = vec![
            "run".into(),
            "--rm".into(),
            "--tty".into(),
            "--interactive".into(),
            "--privileged".into(),
        ];
        if let Ok(opts) = henv::var(DOCKER_OPTS_ENVVAR) {
            let opts = opts.split(" ")
                .map(|v| v.into())
                // Ensure we're not passing something like `--tty` again here.
                .filter(|v| !cmd_args.contains(v))
                .collect::<Vec<_>>();
            if !opts.is_empty() {
                debug!(
                    "Adding extra Docker options from {} = {:?}",
                    DOCKER_OPTS_ENVVAR,
                    opts
                );
                cmd_args.extend_from_slice(opts.as_slice());
            }
        }
        for var in env_vars {
            if let Ok(val) = henv::var(var.as_ref()) {
                debug!("Setting container env var: {:?}='{}'", var.as_ref(), val);
                cmd_args.push("--env".into());
                cmd_args.push(format!("{}={}", var.as_ref(), val).into());
            }
        }
        for vol in volumes {
            cmd_args.push("--volume".into());
            cmd_args.push(vol.as_ref().into());
        }
        cmd_args.push(image_identifier().into());
        cmd_args.extend_from_slice(args.as_slice());

        unset_proxy_env_vars();
        Ok(process::become_command(docker_cmd, cmd_args)?)
    }

    fn unset_proxy_env_vars() {
        for var in vec!["http_proxy", "https_proxy"] {
            if let Ok(_) = henv::var(var) {
                debug!("Unsetting process environment variable '{}'", var);
                env::remove_var(var);
            }
        }
    }

    /// Returns the Docker Studio image with tag for the desired version which corresponds to the
    /// same version (minus release) as this program.
    fn image_identifier() -> String {
        let version: Vec<&str> = VERSION.split("/").collect();
        henv::var(DOCKER_IMAGE_ENVVAR).unwrap_or(format!("{}:{}", DOCKER_IMAGE, version[0]))
    }

    fn is_windows_studio(args: &Vec<OsString>) -> bool {
        if cfg!(not(target_os = "windows")) {
            return false;
        }

        for arg in args.iter() {
            let str_arg = arg.to_string_lossy().to_lowercase();
            if str_arg == String::from("--windows") || str_arg == String::from("-w") {
                return true;
            }
        }

        return false;
    }

    #[cfg(test)]
    mod tests {
        use super::{image_identifier, DOCKER_IMAGE};
        use VERSION;

        #[test]
        fn retrieve_image_identifier() {
            assert_eq!(image_identifier(), format!("{}:{}", DOCKER_IMAGE, VERSION));
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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.

pub mod helpers;
mod context;

use std::fmt;
use std::ops::{Deref, DerefMut};
use std::result;

use hcore::fs::FS_ROOT_PATH;
use handlebars::{Handlebars, RenderError};
use serde::Serialize;
use serde_json;

pub use self::context::RenderContext;
use error::{Error, Result};

static LOGKEY: &'static str = "TP";

pub type RenderResult<T> = result::Result<T, RenderError>;

pub struct TemplateRenderer(Handlebars);

impl TemplateRenderer {
    pub fn new() -> Self {
        let mut handlebars = Handlebars::new();
        handlebars.register_helper("eachAlive", Box::new(helpers::EACH_ALIVE));
        handlebars.register_helper("pkgPathFor", Box::new(helpers::PKG_PATH_FOR));
        handlebars.register_helper("strConcat", Box::new(helpers::STR_CONCAT));
        handlebars.register_helper("strJoin", Box::new(helpers::STR_JOIN));
        handlebars.register_helper("strReplace", Box::new(helpers::STR_REPLACE));
        handlebars.register_helper("toUppercase", Box::new(helpers::TO_UPPERCASE));
        handlebars.register_helper("toLowercase", Box::new(helpers::TO_LOWERCASE));
        handlebars.register_helper("toJson", Box::new(helpers::TO_JSON));
        handlebars.register_helper("toToml", Box::new(helpers::TO_TOML));
        handlebars.register_helper("toYaml", Box::new(helpers::TO_YAML));

        handlebars.register_escape_fn(never_escape);
        TemplateRenderer(handlebars)
    }

    pub fn render<T>(&self, template: &str, ctx: &T) -> Result<String>
    where
        T: Serialize,
    {
        let raw = serde_json::to_value(ctx).map_err(|e| {
            sup_error!(Error::RenderContextSerialization(e))
        })?;
        debug!("Rendering template with context, {}, {}", template, raw);
        self.0.render(template, &raw).map_err(|e| {
            sup_error!(Error::TemplateRenderError(e))
        })
    }
}

impl fmt::Debug for TemplateRenderer {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Handlebars TemplateRenderer")
    }
}

impl Deref for TemplateRenderer {
    type Target = Handlebars;

    fn deref(&self) -> &Handlebars {
        &self.0
    }
}

impl DerefMut for TemplateRenderer {
    fn deref_mut(&mut self) -> &mut Handlebars {
        &mut self.0
    }
}

/// Disables HTML escaping which is enabled by default in Handlebars.
fn never_escape(data: &str) -> String {
    String::from(data)
}

#[cfg(test)]
mod test {
    use std::fs::File;
    use std::io::Read;
    use std::path::PathBuf;
    use std::collections::BTreeMap;
    use serde_json;
    use toml;

    use super::*;
    use util::convert;

    pub fn root() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests")
    }

    pub fn fixtures() -> PathBuf {
        root().join("fixtures")
    }

    pub fn templates() -> PathBuf {
        fixtures().join("templates")
    }

    pub fn sample_configs() -> PathBuf {
        fixtures().join("sample_configs")
    }

    pub fn service_config_json_from_toml_file(filename: &str) -> serde_json::Value {
        let mut file = File::open(sample_configs().join(filename)).unwrap();
        let mut config = String::new();
        let _ = file.read_to_string(&mut config).unwrap();
        let toml = toml::de::from_str(&config).unwrap();
        let data = convert::toml_to_json(toml::Value::Table(toml));
        data
    }

    #[test]
    fn test_handlebars_json_helper() {
        let content = "{{toJson x}}".to_string();
        let mut data = BTreeMap::new();
        data.insert("test".into(), "something".into());

        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
        m.insert("x".into(), data);

        let r = renderer.render("t", &m);

        assert_eq!(
            r.ok().unwrap(),
            r#"{
  "test": "something"
}"#
                .to_string()
        );
    }

    #[test]
    fn test_handlebars_toml_helper() {
        let content = "{{toToml x}}".to_string();
        let mut data = BTreeMap::new();
        data.insert("test".into(), "something".into());

        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
        m.insert("x".into(), data);

        let r = renderer.render("t", &m);

        assert_eq!(
            r.ok().unwrap(),
            r#"test = "something"
"#
                .to_string()
        );
    }

    #[test]
    fn test_handlebars_yaml_helper() {
        let content = "{{toYaml x}}".to_string();
        let mut data = BTreeMap::new();
        data.insert("test".into(), "something".into());

        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
        m.insert("x".into(), data);

        let r = renderer.render("t", &m);

        assert_eq!(
            r.ok().unwrap(),
            r#"---
test: something"#
                .to_string()
        );
    }

    #[test]
    fn to_uppercase_helper() {
        let content = "{{toUppercase var}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, String> = BTreeMap::new();
        m.insert("var".into(), "value".into());
        let rendered = renderer.render("t", &m).unwrap();
        assert_eq!(rendered, "VALUE".to_string());
    }

    #[test]
    fn to_lowercase_helper() {
        let content = "{{toLowercase var}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, String> = BTreeMap::new();
        m.insert("var".into(), "VALUE".into());
        let rendered = renderer.render("t", &m).unwrap();
        assert_eq!(rendered, "value".to_string());
    }

    #[test]
    fn str_replace_helper() {
        let content = "{{strReplace var old new}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, String> = BTreeMap::new();
        m.insert("var".into(), "this is old".into());
        m.insert("old".into(), "old".into());
        m.insert("new".into(), "new".into());
        let rendered = renderer.render("t", &m).unwrap();
        assert_eq!(rendered, "this is new".to_string());
    }

    #[test]
    fn bind_variable() {
        let content = "{{bind.foo.members[0].sys.ip}}";
        let mut renderer = TemplateRenderer::new();
        let data = service_config_json_from_toml_file("complex_config.toml");

        renderer.register_template_string("t", content).unwrap();

        let rendered = renderer.render("t", &data).unwrap();
        assert_eq!(rendered, "172.17.0.5");
    }

    #[test]
    fn pkg_path_for_helper() {
        let content = "{{pkgPathFor \"core/acl\"}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let data = service_config_json_from_toml_file("complex_config.toml");
        let rendered = renderer.render("t", &data).unwrap();
        assert_eq!(
            PathBuf::from(rendered),
            PathBuf::from((&*FS_ROOT_PATH).join(
                "/hab/pkgs/core/acl/2.2.52/20161208223311",
            ))
        );
    }

    #[test]
    fn each_alive_helper_content() {
        let mut renderer = TemplateRenderer::new();
        // template using the new `eachAlive` helper
        renderer
            .register_template_file("each_alive", templates().join("each_alive.txt"))
            .unwrap();

        // template using an each block with a nested if block filtering on `alive`
        renderer
            .register_template_file("all_members", templates().join("all_members.txt"))
            .unwrap();

        let data = service_config_json_from_toml_file("multiple_supervisors_config.toml");

        let each_alive_render = renderer.render("each_alive", &data).unwrap();
        let each_if_render = renderer.render("all_members", &data).unwrap();

        assert_eq!(each_alive_render, each_if_render);
    }

    #[test]
    fn each_alive_helper_first_node() {
        let mut renderer = TemplateRenderer::new();
        // template using the new `eachAlive` helper
        renderer
            .register_template_file("each_alive", templates().join("each_alive.txt"))
            .unwrap();

        // template using an each block with a nested if block filtering on `alive`
        renderer
            .register_template_file("all_members", templates().join("all_members.txt"))
            .unwrap();

        let data = service_config_json_from_toml_file("one_supervisor_not_started.toml");

        let each_alive_render = renderer.render("each_alive", &data).unwrap();
        let each_if_render = renderer.render("all_members", &data).unwrap();

        assert_eq!(each_alive_render, each_if_render);
    }

    #[test]
    fn each_alive_helper_with_identifier_alias() {
        let mut renderer = TemplateRenderer::new();
        // template using the new `eachAlive` helper
        renderer
            .register_template_file(
                "each_alive",
                templates().join("each_alive_with_identifier.txt"),
            )
            .unwrap();

        // template using an each block with a nested if block filtering on `alive`
        renderer
            .register_template_file("all_members", templates().join("all_members.txt"))
            .unwrap();

        let data = service_config_json_from_toml_file("multiple_supervisors_config.toml");

        let each_alive_render = renderer.render("each_alive", &data).unwrap();
        let each_if_render = renderer.render("all_members", &data).unwrap();

        assert_eq!(each_alive_render, each_if_render);
    }
}
<commit_msg>move FS_ROOT usage inside test to avoid warn<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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.

pub mod helpers;
mod context;

use std::fmt;
use std::ops::{Deref, DerefMut};
use std::result;

use handlebars::{Handlebars, RenderError};
use serde::Serialize;
use serde_json;

pub use self::context::RenderContext;
use error::{Error, Result};

static LOGKEY: &'static str = "TP";

pub type RenderResult<T> = result::Result<T, RenderError>;

pub struct TemplateRenderer(Handlebars);

impl TemplateRenderer {
    pub fn new() -> Self {
        let mut handlebars = Handlebars::new();
        handlebars.register_helper("eachAlive", Box::new(helpers::EACH_ALIVE));
        handlebars.register_helper("pkgPathFor", Box::new(helpers::PKG_PATH_FOR));
        handlebars.register_helper("strConcat", Box::new(helpers::STR_CONCAT));
        handlebars.register_helper("strJoin", Box::new(helpers::STR_JOIN));
        handlebars.register_helper("strReplace", Box::new(helpers::STR_REPLACE));
        handlebars.register_helper("toUppercase", Box::new(helpers::TO_UPPERCASE));
        handlebars.register_helper("toLowercase", Box::new(helpers::TO_LOWERCASE));
        handlebars.register_helper("toJson", Box::new(helpers::TO_JSON));
        handlebars.register_helper("toToml", Box::new(helpers::TO_TOML));
        handlebars.register_helper("toYaml", Box::new(helpers::TO_YAML));

        handlebars.register_escape_fn(never_escape);
        TemplateRenderer(handlebars)
    }

    pub fn render<T>(&self, template: &str, ctx: &T) -> Result<String>
    where
        T: Serialize,
    {
        let raw = serde_json::to_value(ctx).map_err(|e| {
            sup_error!(Error::RenderContextSerialization(e))
        })?;
        debug!("Rendering template with context, {}, {}", template, raw);
        self.0.render(template, &raw).map_err(|e| {
            sup_error!(Error::TemplateRenderError(e))
        })
    }
}

impl fmt::Debug for TemplateRenderer {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Handlebars TemplateRenderer")
    }
}

impl Deref for TemplateRenderer {
    type Target = Handlebars;

    fn deref(&self) -> &Handlebars {
        &self.0
    }
}

impl DerefMut for TemplateRenderer {
    fn deref_mut(&mut self) -> &mut Handlebars {
        &mut self.0
    }
}

/// Disables HTML escaping which is enabled by default in Handlebars.
fn never_escape(data: &str) -> String {
    String::from(data)
}

#[cfg(test)]
mod test {
    use std::fs::File;
    use std::io::Read;
    use std::path::PathBuf;
    use std::collections::BTreeMap;
    use serde_json;
    use toml;

    use super::*;
    use hcore::fs::FS_ROOT_PATH;
    use util::convert;

    pub fn root() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests")
    }

    pub fn fixtures() -> PathBuf {
        root().join("fixtures")
    }

    pub fn templates() -> PathBuf {
        fixtures().join("templates")
    }

    pub fn sample_configs() -> PathBuf {
        fixtures().join("sample_configs")
    }

    pub fn service_config_json_from_toml_file(filename: &str) -> serde_json::Value {
        let mut file = File::open(sample_configs().join(filename)).unwrap();
        let mut config = String::new();
        let _ = file.read_to_string(&mut config).unwrap();
        let toml = toml::de::from_str(&config).unwrap();
        let data = convert::toml_to_json(toml::Value::Table(toml));
        data
    }

    #[test]
    fn test_handlebars_json_helper() {
        let content = "{{toJson x}}".to_string();
        let mut data = BTreeMap::new();
        data.insert("test".into(), "something".into());

        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
        m.insert("x".into(), data);

        let r = renderer.render("t", &m);

        assert_eq!(
            r.ok().unwrap(),
            r#"{
  "test": "something"
}"#
                .to_string()
        );
    }

    #[test]
    fn test_handlebars_toml_helper() {
        let content = "{{toToml x}}".to_string();
        let mut data = BTreeMap::new();
        data.insert("test".into(), "something".into());

        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
        m.insert("x".into(), data);

        let r = renderer.render("t", &m);

        assert_eq!(
            r.ok().unwrap(),
            r#"test = "something"
"#
                .to_string()
        );
    }

    #[test]
    fn test_handlebars_yaml_helper() {
        let content = "{{toYaml x}}".to_string();
        let mut data = BTreeMap::new();
        data.insert("test".into(), "something".into());

        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
        m.insert("x".into(), data);

        let r = renderer.render("t", &m);

        assert_eq!(
            r.ok().unwrap(),
            r#"---
test: something"#
                .to_string()
        );
    }

    #[test]
    fn to_uppercase_helper() {
        let content = "{{toUppercase var}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, String> = BTreeMap::new();
        m.insert("var".into(), "value".into());
        let rendered = renderer.render("t", &m).unwrap();
        assert_eq!(rendered, "VALUE".to_string());
    }

    #[test]
    fn to_lowercase_helper() {
        let content = "{{toLowercase var}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, String> = BTreeMap::new();
        m.insert("var".into(), "VALUE".into());
        let rendered = renderer.render("t", &m).unwrap();
        assert_eq!(rendered, "value".to_string());
    }

    #[test]
    fn str_replace_helper() {
        let content = "{{strReplace var old new}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let mut m: BTreeMap<String, String> = BTreeMap::new();
        m.insert("var".into(), "this is old".into());
        m.insert("old".into(), "old".into());
        m.insert("new".into(), "new".into());
        let rendered = renderer.render("t", &m).unwrap();
        assert_eq!(rendered, "this is new".to_string());
    }

    #[test]
    fn bind_variable() {
        let content = "{{bind.foo.members[0].sys.ip}}";
        let mut renderer = TemplateRenderer::new();
        let data = service_config_json_from_toml_file("complex_config.toml");

        renderer.register_template_string("t", content).unwrap();

        let rendered = renderer.render("t", &data).unwrap();
        assert_eq!(rendered, "172.17.0.5");
    }

    #[test]
    fn pkg_path_for_helper() {
        let content = "{{pkgPathFor \"core/acl\"}}".to_string();
        let mut renderer = TemplateRenderer::new();
        renderer.register_template_string("t", content).unwrap();

        let data = service_config_json_from_toml_file("complex_config.toml");
        let rendered = renderer.render("t", &data).unwrap();
        assert_eq!(
            PathBuf::from(rendered),
            PathBuf::from((&*FS_ROOT_PATH).join(
                "/hab/pkgs/core/acl/2.2.52/20161208223311",
            ))
        );
    }

    #[test]
    fn each_alive_helper_content() {
        let mut renderer = TemplateRenderer::new();
        // template using the new `eachAlive` helper
        renderer
            .register_template_file("each_alive", templates().join("each_alive.txt"))
            .unwrap();

        // template using an each block with a nested if block filtering on `alive`
        renderer
            .register_template_file("all_members", templates().join("all_members.txt"))
            .unwrap();

        let data = service_config_json_from_toml_file("multiple_supervisors_config.toml");

        let each_alive_render = renderer.render("each_alive", &data).unwrap();
        let each_if_render = renderer.render("all_members", &data).unwrap();

        assert_eq!(each_alive_render, each_if_render);
    }

    #[test]
    fn each_alive_helper_first_node() {
        let mut renderer = TemplateRenderer::new();
        // template using the new `eachAlive` helper
        renderer
            .register_template_file("each_alive", templates().join("each_alive.txt"))
            .unwrap();

        // template using an each block with a nested if block filtering on `alive`
        renderer
            .register_template_file("all_members", templates().join("all_members.txt"))
            .unwrap();

        let data = service_config_json_from_toml_file("one_supervisor_not_started.toml");

        let each_alive_render = renderer.render("each_alive", &data).unwrap();
        let each_if_render = renderer.render("all_members", &data).unwrap();

        assert_eq!(each_alive_render, each_if_render);
    }

    #[test]
    fn each_alive_helper_with_identifier_alias() {
        let mut renderer = TemplateRenderer::new();
        // template using the new `eachAlive` helper
        renderer
            .register_template_file(
                "each_alive",
                templates().join("each_alive_with_identifier.txt"),
            )
            .unwrap();

        // template using an each block with a nested if block filtering on `alive`
        renderer
            .register_template_file("all_members", templates().join("all_members.txt"))
            .unwrap();

        let data = service_config_json_from_toml_file("multiple_supervisors_config.toml");

        let each_alive_render = renderer.render("each_alive", &data).unwrap();
        let each_if_render = renderer.render("all_members", &data).unwrap();

        assert_eq!(each_alive_render, each_if_render);
    }
}
<|endoftext|>


<commit_before>
use na::{Rotation};
use ecs::{DataHelper, EntityIter, System};
use ecs::system::entity::{EntityProcess};
use protobuf::repeated::RepeatedField;

use contracts::actions::{Active};
use contracts::ship::{PhysicsState, Ship}; // TODO move out for this reason
use contracts::world::{Snapshot};

use sim::{JmpComponents, JmpServices};

pub struct SnapshotProcess;

impl System for SnapshotProcess { type Components = JmpComponents; type Services = JmpServices; }

impl EntityProcess for SnapshotProcess {
    fn process(&mut self, entities: EntityIter<JmpComponents>, data: &mut DataHelper<JmpComponents, JmpServices>) {
        let mut snapshot = Snapshot::new();
        let mut ships: Vec<Ship> = Vec::new();
        for e in entities {
            let mut ship = Ship::new();
            ship.set_entityId(e.id());
            let structure = data.structure[e].contract();
            ship.set_structure(structure);
            let ref body = data.physics_handle[e].handle.borrow();
            let mut physics_state = PhysicsState::new();
            physics_state.set_x(body.position().translation.x);
            physics_state.set_y(body.position().translation.y);
            physics_state.set_theta(body.position().rotation.rotation().x);
            physics_state.set_vx(body.lin_vel().x);
            physics_state.set_vy(body.lin_vel().y);
            physics_state.set_omega(body.ang_vel().x);
            ship.set_physicsState(physics_state);
            let mut active = Active::new();
            active.set_groups(data.active_thrusters[e].clone());
            ship.set_active(active);
            ships.push(ship);
        }
        println!("Serializing {} structures in snapshot", ships.len());
        snapshot.set_ships(RepeatedField::from_vec(ships));
        data.services.snapshot = Some(snapshot);
    }
}<commit_msg>moved it!<commit_after>
use na::{Rotation};
use ecs::{DataHelper, EntityIter, System};
use ecs::system::entity::{EntityProcess};
use protobuf::repeated::RepeatedField;

use contracts::actions::{Active};
use contracts::ship::{PhysicsState, Ship};
use contracts::world::{Snapshot};

use sim::{JmpComponents, JmpServices};

pub struct SnapshotProcess;

impl System for SnapshotProcess { type Components = JmpComponents; type Services = JmpServices; }

impl EntityProcess for SnapshotProcess {
    fn process(&mut self, entities: EntityIter<JmpComponents>, data: &mut DataHelper<JmpComponents, JmpServices>) {
        let mut snapshot = Snapshot::new();
        let mut ships: Vec<Ship> = Vec::new();
        for e in entities {
            let mut ship = Ship::new();
            ship.set_entityId(e.id());
            let structure = data.structure[e].contract();
            ship.set_structure(structure);
            let ref body = data.physics_handle[e].handle.borrow();
            let mut physics_state = PhysicsState::new();
            physics_state.set_x(body.position().translation.x);
            physics_state.set_y(body.position().translation.y);
            physics_state.set_theta(body.position().rotation.rotation().x);
            physics_state.set_vx(body.lin_vel().x);
            physics_state.set_vy(body.lin_vel().y);
            physics_state.set_omega(body.ang_vel().x);
            ship.set_physicsState(physics_state);
            let mut active = Active::new();
            active.set_groups(data.active_thrusters[e].clone());
            ship.set_active(active);
            ships.push(ship);
        }
        println!("Serializing {} structures in snapshot", ships.len());
        snapshot.set_ships(RepeatedField::from_vec(ships));
        data.services.snapshot = Some(snapshot);
    }
}<|endoftext|>


<commit_before>use seed::prelude::*;
use crate::Msg;

mod perfects_utils {
	pub struct Perfect<'a> {
		pub n: u64,
		pub p: &'a str,
		pub digits: u64,
		pub discovery: &'a str,
	}

	pub fn perfects<'a>() -> Vec<Perfect<'a>> {
        vec![
			Perfect {n: 51, p: "282589933 × (282589934-1)", digits: 49724095, discovery: "2018 Laroche, Woltman, Kurowski, Blosser, et al."},
			Perfect {n: 50, p: "277232916 × (277232917-1)", digits: 46498850, discovery: "2017 Jonathan Pace, George Woltman, Scott Kurowski, Aaron Blosser, et al.." },
			Perfect {n: 49, p: "274207280 × (274207281-1)", digits: 44677235, discovery: "2016 Cooper, Woltman, Kurowski, Blosser et al." },
			Perfect {n: 48, p: "257885160 × (257885161-1)", digits: 34850340, discovery: "2013 Cooper, Woltman, Kurowski, et al." },
			Perfect {n: 47, p: "243112608 × (243112609-1)", digits: 25956377, discovery: "2008 Smith, Woltman, Kurowski, et al." },
			Perfect {n: 46, p: "242643800 × (242643801-1)", digits: 42643801, discovery: "2009 Strindmo, Woltman, Kurowski, et al." },
			Perfect {n: 45, p: "237156666 × (237156667-1)", digits: 22370543, discovery: "2008 Elvenich, Woltman, Kurowski, et al." },
			Perfect {n: 44, p: "232582656 × (232582657-1)", digits: 19616714, discovery: "2006 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 43, p: "230402456 × (230402457-1)", digits: 18304103, discovery: "2005 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 42, p: "225964950 × (225964951-1)", digits: 15632458, discovery: "2005 Nowak, Woltman, Kurowski, et. al." },
			Perfect {n: 41, p: "224036582 × (224036583-1)", digits: 14471465, discovery: "2004 Findley, Woltman, Kurowski, et. al." },
			Perfect {n: 40, p: "220996010 × (220996011-1)", digits: 12640858, discovery: "2003 Shafer, Woltman, Kurowski, et. al." },
			Perfect {n: 39, p: "213466916 × (213466917-1)", digits: 8107892, discovery: "2001 Cameron, Woltman, Kurowski, et. al." },
			Perfect {n: 38, p: "26972592 × (26972593-1)", digits: 4197919, discovery: "1999 Hajratwala, Woltman, Kurowski, et. al." },
			Perfect {n: 37, p: "23021376 × (23021377-1)", digits: 1819050, discovery: "1998 Clarkson, Woltman, Kurowski, et. al." },
			Perfect {n: 36, p: "22976220 × (22976221-1)", digits: 1791864, discovery: "1997 Spence, Woltman, et. al." },
			Perfect {n: 35, p: "21398268 × (21398269-1)", digits: 841842, discovery: "1996 Armengaud, Woltman, et. al." },
			Perfect {n: 34, p: "21257786 × (21257787-1)", digits: 757263, discovery: "1996 Slowinski&Gage" },
			Perfect {n: 33, p: "2859432 × (2859433-1)", digits: 517430, discovery: "1994 Slowinski&Gage" },
			Perfect {n: 32, p: "2756838 × (2756839-1)", digits: 455663, discovery: "1992 Slowinski&Gage" },
			Perfect {n: 31, p: "2216090 × (2216091-1)", digits: 130100, discovery: "1985 Slowinski" },
			Perfect {n: 30, p: "2132048 × (2132049-1)", digits: 79502, discovery: "1983 Slowinski" },
			Perfect {n: 29, p: "2110502 × (2110503-1)", digits: 66530, discovery: "1988 Colquitt&Welsh" },
			Perfect {n: 28, p: "286242 × (286243-1)", digits: 51924, discovery: "1982 Slowinski" },
			Perfect {n: 27, p: "244496 × (244497-1)", digits: 26790, discovery: "1979 Nelson&Slowinski" },
			Perfect {n: 26, p: "223208 × (223209-1)", digits: 13973, discovery: "1979 Noll" },
			Perfect {n: 25, p: "221700 × (221701-1)", digits: 13066, discovery: "1978 Noll&Nickel" },
			Perfect {n: 24, p: "219936 × (219937-1)", digits: 12003, discovery: "1971 Tuckerman" },
			Perfect {n: 23, p: "211212 × (211213-1)", digits: 6751, discovery: "1963 Gillies" },
			Perfect {n: 22, p: "29940 × (29941-1)", digits: 5985, discovery: "1963 Gillies" },
			Perfect {n: 21, p: "29688 × (29689-1)", digits: 5834, discovery: "1963 Gillies" },
			Perfect {n: 20, p: "24422 × (24423-1)", digits: 2663, discovery: "1961 Hurwitz" },
			Perfect {n: 19, p: "24252 × (24253-1)", digits: 2561, discovery: "1961 Hurwitz" },
			Perfect {n: 18, p: "23216 × (23217-1)", digits: 1937, discovery: "1957 Riesel" },
			Perfect {n: 17, p: "22280 × (22281-1)", digits: 1373, discovery: "1952 Robinson" },
			Perfect {n: 16, p: "22202 × (22203-1)", digits: 1327, discovery: "1952 Robinson" },
			Perfect {n: 15, p: "21278 × (21279-1)", digits: 770, discovery: "1952 Robinson" },
			Perfect {n: 14, p: "2606 × (2607-1)", digits: 366, discovery: "1952 Robinson" },
			Perfect {n: 13, p: "2520 × (2521-1)", digits: 314, discovery: "1952 Robinson" },
			Perfect {n: 12, p: "2126 × (2127-1)", digits: 77, discovery: "1876 Lucas" },
			Perfect {n: 11, p: "2106 × (2107-1)", digits: 65, discovery: "1914 Powers" },
			Perfect {n: 10, p: "288 × (289-1)", digits: 54, discovery: "1911 Powers" },
			Perfect {n: 9, p: "260 × (261-1)", digits: 37, discovery: "1883 Pervushin" },
			Perfect {n: 8, p: "230 × (231-1)", digits: 19, discovery: "1772 Euler" },
			Perfect {n: 7, p: "218 × (219-1)", digits: 12, discovery: "1588 Cataldi" },
			Perfect {n: 6, p: "216 × (217-1)", digits: 10, discovery: "1588 Cataldi" },
			Perfect {n: 5, p: "212 × (213-1)", digits: 8, discovery: "1456 ?" },
			Perfect {n: 4, p: "26 × (27-1)", digits: 4, discovery: "?" },
			Perfect {n: 3, p: "24 × (25-1)", digits: 3, discovery: "?" },
			Perfect {n: 2, p: "22 × (23-1)", digits: 2, discovery: "?" },
			Perfect {n: 1, p: "21 × (23-1)", digits: 1, discovery: "?" },
		]
	}
}

pub fn render() -> seed::dom_types::Node<Msg> {
    let mut html = vec![];

    let perfects = perfects_utils::perfects();

    for n in 0..perfects.len() {
        let download_txt:String = vec!["https://static.bigprimes.net/archive/perfect/",&perfects[n].n.to_string(),".txt"].into_iter().collect();
        let download_zip:String = vec!["https://static.bigprimes.net/archive/perfect/",&perfects[n].n.to_string(),".zip"].into_iter().collect();
        html.push(
            tr![
                td![perfects[n].n.to_string()],//rank
                td![El::from_html(perfects[n].p)],//perfect number as a formula
                td![perfects[n].digits.to_string()],//digits in length
                td![perfects[n].discovery],//disocvery
                if perfects[n].n >= 25 {a![attrs!{At::Href => download_zip},"ZIP"]} else {a![attrs!{At::Href => download_txt},"TXT"]}//downloads
            ]
        );
    }

    div![
        h1!["The Perfect Numbers"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "perfecttable text"},
            tbody![
                tr![
                    td![
                        b!["No."]
                    ],
                    td![
                        b!["Prime"]
                    ],
                    td![
                        b!["Digits"]
                    ],
                    td![
                        b!["Discovered"]
                    ],
                    td![
                        b!["Download"]
                    ]
                ],
                html
            ]
        ]
    ]
}

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

    #[test]
    fn perfect_test<'a>() {
        let mut perfects:Vec<perfects_utils::Perfect<'a>> = perfects_utils::perfects();
		perfects.reverse();
        assert_eq!(perfects[49].digits, 46498850);
        assert_eq!(perfects[39].digits, 12640858);
    }
}<commit_msg>Replaced html duplication with stricter struct and equation var<commit_after>use seed::prelude::*;
use crate::Msg;

mod perfects_utils {
	pub struct Perfect<'a> {
		pub n: u64,
		pub p: u64,
		pub digits: u64,
		pub discovery: &'a str,
	}

	pub fn perfects<'a>() -> Vec<Perfect<'a>> {
        vec![
			Perfect {n: 51, p: 82589934, digits: 49724095, discovery: "2018 Laroche, Woltman, Kurowski, Blosser, et al."},
			Perfect {n: 50, p: 77232917, digits: 46498850, discovery: "2017 Jonathan Pace, George Woltman, Scott Kurowski, Aaron Blosser, et al.." },
			Perfect {n: 49, p: 74207281, digits: 44677235, discovery: "2016 Cooper, Woltman, Kurowski, Blosser et al." },
			Perfect {n: 48, p: 57885161, digits: 34850340, discovery: "2013 Cooper, Woltman, Kurowski, et al." },
			Perfect {n: 47, p: 43112609, digits: 25956377, discovery: "2008 Smith, Woltman, Kurowski, et al." },
			Perfect {n: 46, p: 42643801, digits: 42643801, discovery: "2009 Strindmo, Woltman, Kurowski, et al." },
			Perfect {n: 45, p: 37156667, digits: 22370543, discovery: "2008 Elvenich, Woltman, Kurowski, et al." },
			Perfect {n: 44, p: 32582657, digits: 19616714, discovery: "2006 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 43, p: 30402457, digits: 18304103, discovery: "2005 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 42, p: 25964951, digits: 15632458, discovery: "2005 Nowak, Woltman, Kurowski, et. al." },
			Perfect {n: 41, p: 24036583, digits: 14471465, discovery: "2004 Findley, Woltman, Kurowski, et. al." },
			Perfect {n: 40, p: 20996011, digits: 12640858, discovery: "2003 Shafer, Woltman, Kurowski, et. al." },
			Perfect {n: 39, p: 13466917, digits: 8107892, discovery: "2001 Cameron, Woltman, Kurowski, et. al." },
			Perfect {n: 38, p: 6972593, digits: 4197919, discovery: "1999 Hajratwala, Woltman, Kurowski, et. al." },
			Perfect {n: 37, p: 3021377, digits: 1819050, discovery: "1998 Clarkson, Woltman, Kurowski, et. al." },
			Perfect {n: 36, p: 2976221, digits: 1791864, discovery: "1997 Spence, Woltman, et. al." },
			Perfect {n: 35, p: 1398269, digits: 841842, discovery: "1996 Armengaud, Woltman, et. al." },
			Perfect {n: 34, p: 1257787, digits: 757263, discovery: "1996 Slowinski&Gage" },
			Perfect {n: 33, p: 859433, digits: 517430, discovery: "1994 Slowinski&Gage" },
			Perfect {n: 32, p: 756839, digits: 455663, discovery: "1992 Slowinski&Gage" },
			Perfect {n: 31, p: 216091, digits: 130100, discovery: "1985 Slowinski" },
			Perfect {n: 30, p: 132049, digits: 79502, discovery: "1983 Slowinski" },
			Perfect {n: 29, p: 110503, digits: 66530, discovery: "1988 Colquitt&Welsh" },
			Perfect {n: 28, p: 86243, digits: 51924, discovery: "1982 Slowinski" },
			Perfect {n: 27, p: 44497, digits: 26790, discovery: "1979 Nelson&Slowinski" },
			Perfect {n: 26, p: 23209, digits: 13973, discovery: "1979 Noll" },
			Perfect {n: 25, p: 21701, digits: 13066, discovery: "1978 Noll&Nickel" },
			Perfect {n: 24, p: 19937, digits: 12003, discovery: "1971 Tuckerman" },
			Perfect {n: 23, p: 11213, digits: 6751, discovery: "1963 Gillies" },
			Perfect {n: 22, p: 9941, digits: 5985, discovery: "1963 Gillies" },
			Perfect {n: 21, p: 9689, digits: 5834, discovery: "1963 Gillies" },
			Perfect {n: 20, p: 4423, digits: 2663, discovery: "1961 Hurwitz" },
			Perfect {n: 19, p: 4253, digits: 2561, discovery: "1961 Hurwitz" },
			Perfect {n: 18, p: 3217, digits: 1937, discovery: "1957 Riesel" },
			Perfect {n: 17, p: 2281, digits: 1373, discovery: "1952 Robinson" },
			Perfect {n: 16, p: 2203, digits: 1327, discovery: "1952 Robinson" },
			Perfect {n: 15, p: 1279, digits: 770, discovery: "1952 Robinson" },
			Perfect {n: 14, p: 607, digits: 366, discovery: "1952 Robinson" },
			Perfect {n: 13, p: 521, digits: 314, discovery: "1952 Robinson" },
			Perfect {n: 12, p: 127, digits: 77, discovery: "1876 Lucas" },
			Perfect {n: 11, p: 107, digits: 65, discovery: "1914 Powers" },
			Perfect {n: 10, p: 89, digits: 54, discovery: "1911 Powers" },
			Perfect {n: 9, p: 61, digits: 37, discovery: "1883 Pervushin" },
			Perfect {n: 8, p: 31, digits: 19, discovery: "1772 Euler" },
			Perfect {n: 7, p: 19, digits: 12, discovery: "1588 Cataldi" },
			Perfect {n: 6, p: 17, digits: 10, discovery: "1588 Cataldi" },
			Perfect {n: 5, p: 13, digits: 8, discovery: "1456 ?" },
			Perfect {n: 4, p: 7, digits: 4, discovery: "?" },
			Perfect {n: 3, p: 5, digits: 3, discovery: "?" },
			Perfect {n: 2, p: 3, digits: 2, discovery: "?" },
			Perfect {n: 1, p: 3, digits: 1, discovery: "?" },
		]
	}
}

pub fn render() -> seed::dom_types::Node<Msg> {
    let mut html = vec![];

    let perfects = perfects_utils::perfects();

    for n in 0..perfects.len() {
        let download_txt:String = vec!["https://static.bigprimes.net/archive/perfect/",&perfects[n].n.to_string(),".txt"].into_iter().collect();
        let download_zip:String = vec!["https://static.bigprimes.net/archive/perfect/",&perfects[n].n.to_string(),".zip"].into_iter().collect();

		let equation:String = vec!["2",&(perfects[n].p-1).to_string()," × (2",&(perfects[n].p).to_string(),"-1)"].into_iter().collect();

        html.push(
            tr![
                td![perfects[n].n.to_string()],//rank
				td![El::from_html(&equation)],//perfect number as a formula

                td![perfects[n].digits.to_string()],//digits in length
                td![perfects[n].discovery],//disocvery
                if perfects[n].n >= 25 {a![attrs!{At::Href => download_zip},"ZIP"]} else {a![attrs!{At::Href => download_txt},"TXT"]}//downloads
            ]
        );
    }

    div![
        h1!["The Perfect Numbers"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "perfecttable text"},
            tbody![
                tr![
                    td![
                        b!["No."]
                    ],
                    td![
                        b!["Prime"]
                    ],
                    td![
                        b!["Digits"]
                    ],
                    td![
                        b!["Discovered"]
                    ],
                    td![
                        b!["Download"]
                    ]
                ],
                html
            ]
        ]
    ]
}

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

    #[test]
    fn perfect_test<'a>() {
        let mut perfects:Vec<perfects_utils::Perfect<'a>> = perfects_utils::perfects();
		perfects.reverse();
        assert_eq!(perfects[49].digits, 46498850);
        assert_eq!(perfects[39].digits, 12640858);
    }
}<|endoftext|>