<commit_before>//! Defines the BSDF which acts as a container for composing the various BRDFs
//! and BTDFs that describe the surface's properties

use std::vec::Vec;
use collect::enum_set::EnumSet;

use linalg;
use linalg::{Normal, Vector};
use film::Colorf;
use geometry::DifferentialGeometry;
use bxdf::{BxDF, BxDFType};

/// The BSDF contains the various BRDFs and BTDFs that describe the surface's properties
/// at some point. It also transforms incident and outgoing light directions into
/// shading space to make the BxDFs easier to implement.
/// TODO: We really need the memory pool. Each time we get the bsdf from a
/// material we need to allocate a decent amount of stuff since they each need
/// their own tangent, bitangent and differential geometry reference.
pub struct BSDF<'a> {
    /// Shading normal, may be perturbed by bump mapping
    pub n: Normal,
    /// The actual geometry normal
    pub ng: Normal,
    /// Tangent vector for the surface
    pub tan: Vector,
    /// Bitangent vector for the surface
    pub bitan: Vector,
    /// Refractive index of the geometry
    pub eta: f32,
    /// TODO: Currently a Vec is safe to use but once in the memory pool it
    /// will leak since it won't be dropped. This would also migrate our BxDFs
    /// from Box<BxDF> to &BxDF. When unboxed traits land we can move to unboxed
    /// BxDFs here though.
    bxdfs: &'a Vec<Box<BxDF + 'static + Send + Sync>>,
}

impl<'a> BSDF<'a> {
    /// Create a new BSDF using the BxDFs passed to shade the differential geometry with
    /// refractive index `eta`
    pub fn new(bxdfs: &'a Vec<Box<BxDF + 'static + Send + Sync>>, eta: f32,
               dg: &DifferentialGeometry<'a>)
               -> BSDF<'a> {
        let n = dg.n.normalized();
        let tan = linalg::cross(&n,  &dg.dp_du.normalized()).normalized();
        let bitan = linalg::cross(&tan, &n).normalized();
        BSDF { n: n, ng: dg.ng.normalized(), tan: tan, bitan: bitan, bxdfs: bxdfs, eta: eta }
    }
    /// Return the total number of BxDFs
    pub fn num_bxdfs(&self) -> usize { self.bxdfs.len() }
    /// Return the number of BxDFs matching the flags
    pub fn num_matching(&self, flags: EnumSet<BxDFType>) -> usize {
        self.bxdfs.iter().filter(|ref x| x.matches(flags)).count()
    }
    /// Transform the vector from world space to shading space
    pub fn to_shading(&self, v: &Vector) -> Vector {
        Vector::new(linalg::dot(v, &self.bitan), linalg::dot(v, &self.tan),
                    linalg::dot(v, &self.n))
    }
    /// Transform the vectro from shading space to world space
    pub fn from_shading(&self, v: &Vector) -> Vector {
        Vector::new(self.bitan.x * v.x + self.tan.x * v.y + self.n.x * v.z,
                    self.bitan.y * v.x + self.tan.y * v.y + self.n.y * v.z,
                    self.bitan.z * v.x + self.tan.z * v.y + self.n.z * v.z)
    }
    /// Evaluate the BSDF for the outgoing and incident light directions
    /// `w_o` and `w_i` in world space, sampling the desired subset of BxDFs
    /// selected by the flags passed. `wo_world` and `wi_world` should point from
    /// the hit point in the outgoing and incident light directions respectively.
    pub fn eval(&self, wo_world: &Vector, wi_world: &Vector, mut flags: EnumSet<BxDFType>) -> Colorf {
        let w_o = self.to_shading(wo_world);
        let w_i = self.to_shading(wi_world);
        // Determine if we should evaluate reflection or transmission based on the
        // geometry normal and the light directions
        if linalg::dot(wo_world, &self.ng) * linalg::dot(wi_world, &self.ng) > 0.0 {
            flags.remove(&BxDFType::Transmission);
        } else {
            flags.remove(&BxDFType::Reflection);
        }
        // Find all matching BxDFs and add their contribution to the material's color
        self.bxdfs.iter().filter(|ref x| x.matches(flags)).map(|ref x| x.eval(&w_o, &w_i))
            .fold(Colorf::broadcast(0.0), |x, y| x + y)
    }
}

<commit_msg>Add hacky sample method to BSDF and also have it store the hit point<commit_after>//! Defines the BSDF which acts as a container for composing the various BRDFs
//! and BTDFs that describe the surface's properties

use std::vec::Vec;
use collect::enum_set::EnumSet;

use linalg;
use linalg::{Normal, Vector, Point};
use film::Colorf;
use geometry::DifferentialGeometry;
use bxdf::{BxDF, BxDFType};

/// The BSDF contains the various BRDFs and BTDFs that describe the surface's properties
/// at some point. It also transforms incident and outgoing light directions into
/// shading space to make the BxDFs easier to implement.
/// TODO: We really need the memory pool. Each time we get the bsdf from a
/// material we need to allocate a decent amount of stuff since they each need
/// their own tangent, bitangent and differential geometry reference.
pub struct BSDF<'a> {
    /// The hit point
    pub p: Point,
    /// Shading normal, may be perturbed by bump mapping
    pub n: Normal,
    /// The actual geometry normal
    pub ng: Normal,
    /// Tangent vector for the surface
    pub tan: Vector,
    /// Bitangent vector for the surface
    pub bitan: Vector,
    /// Refractive index of the geometry
    pub eta: f32,
    /// TODO: Currently a Vec is safe to use but once in the memory pool it
    /// will leak since it won't be dropped. This would also migrate our BxDFs
    /// from Box<BxDF> to &BxDF. When unboxed traits land we can move to unboxed
    /// BxDFs here though.
    bxdfs: &'a Vec<Box<BxDF + Send + Sync>>,
}

impl<'a> BSDF<'a> {
    /// Create a new BSDF using the BxDFs passed to shade the differential geometry with
    /// refractive index `eta`
    pub fn new(bxdfs: &'a Vec<Box<BxDF + Send + Sync>>, eta: f32,
               dg: &DifferentialGeometry<'a>)
               -> BSDF<'a> {
        let n = dg.n.normalized();
        let tan = linalg::cross(&n,  &dg.dp_du.normalized()).normalized();
        let bitan = linalg::cross(&tan, &n).normalized();
        BSDF { p: dg.p, n: n, ng: dg.ng.normalized(), tan: tan, bitan: bitan, bxdfs: bxdfs, eta: eta }
    }
    /// Return the total number of BxDFs
    pub fn num_bxdfs(&self) -> usize { self.bxdfs.len() }
    /// Return the number of BxDFs matching the flags
    pub fn num_matching(&self, flags: EnumSet<BxDFType>) -> usize {
        self.bxdfs.iter().filter(|ref x| x.matches(flags)).count()
    }
    /// Transform the vector from world space to shading space
    pub fn to_shading(&self, v: &Vector) -> Vector {
        Vector::new(linalg::dot(v, &self.bitan), linalg::dot(v, &self.tan),
                    linalg::dot(v, &self.n))
    }
    /// Transform the vectro from shading space to world space
    pub fn from_shading(&self, v: &Vector) -> Vector {
        Vector::new(self.bitan.x * v.x + self.tan.x * v.y + self.n.x * v.z,
                    self.bitan.y * v.x + self.tan.y * v.y + self.n.y * v.z,
                    self.bitan.z * v.x + self.tan.z * v.y + self.n.z * v.z)
    }
    /// Evaluate the BSDF for the outgoing and incident light directions
    /// `w_o` and `w_i` in world space, sampling the desired subset of BxDFs
    /// selected by the flags passed. `wo_world` and `wi_world` should point from
    /// the hit point in the outgoing and incident light directions respectively.
    pub fn eval(&self, wo_world: &Vector, wi_world: &Vector, mut flags: EnumSet<BxDFType>) -> Colorf {
        let w_o = self.to_shading(wo_world);
        let w_i = self.to_shading(wi_world);
        // Determine if we should evaluate reflection or transmission based on the
        // geometry normal and the light directions
        if linalg::dot(wo_world, &self.ng) * linalg::dot(wi_world, &self.ng) > 0.0 {
            flags.remove(&BxDFType::Transmission);
        } else {
            flags.remove(&BxDFType::Reflection);
        }
        // Find all matching BxDFs and add their contribution to the material's color
        self.bxdfs.iter().filter(|ref x| x.matches(flags)).map(|ref x| x.eval(&w_o, &w_i))
            .fold(Colorf::broadcast(0.0), |x, y| x + y)
    }
    /// Sample a component of the BSDF to get an incident light direction for light
    /// leaving the surface along `w_o`. Returns the color, direction and the type
    /// of the BxDF that was sampled. TODO: Need to take random numbers here for proper
    /// sampling. Currently we just find the first that matches which is so incredibly wrong
    /// for proper sampling.
    pub fn sample(&self, wo_world: &Vector, flags: EnumSet<BxDFType>) -> (Colorf, Vector, EnumSet<BxDFType>) {
        let n_matching = self.num_matching(flags);
        if n_matching == 0 {
            return (Colorf::broadcast(0.0), Vector::broadcast(0.0), EnumSet::new());
        }
        // Really bad: just take the first for now. This will be ok for having very basic
        // materials like our single component diffuse and specular but is wrong for
        // more complicated ones
        let bsdf = self.matching_at(0, flags);
        let w_o = self.to_shading(wo_world);
        let (f, w_i) = bsdf.sample(&w_o);
        // TODO: Will fail for later materials - we assume only one matched. For the diffuse
        // and specular metal materials this is correct but not for more complex ones
        (f, self.from_shading(&w_i), bsdf.bxdf_type())
    }
    /// Get the `i`th BxDF that matches the flags passed. There should not be fewer than i
    /// BxDFs that match the flags
    fn matching_at(&self, i: usize, flags: EnumSet<BxDFType>) -> &Box<BxDF + Send + Sync> {
        let mut it = self.bxdfs.iter().filter(|ref x| x.matches(flags)).skip(i);
        match it.next() {
            Some(b) => b,
            None => panic!("Out of bounds index for BxDF type {:?}", flags)
        }
    }
}

<|endoftext|>


<commit_before>
use std::collections::HashSet;
use std::process::Command;

/// Find dependencies for the given exe/dll
///
/// ignore is a list of dll names that should not
/// be added to deps.
///
/// Returns (format, deps) format is the binary format
/// (e.g. pei-x86-64) deps is a set of dependency dlls.
///
/// Internally this calls objdump -p <file>
///
pub fn deps(file: &str, ignore: &[&str]) -> Result<(String,HashSet<String>),String> {

    let command = Command::new("objdump")
        .arg("-p")
        .arg(file)
        .output().unwrap_or_else(|e| {panic!("Failed to execute objdump: {}", e)});

    if !command.status.success() {
        return Err(format!("Error executing objdump({}): {}",
                 command.status.code().unwrap_or(-1),
                 String::from_utf8_lossy(&command.stderr)));
    }

    let mut deps = HashSet::new();
    let out = String::from_utf8_lossy(&command.stdout);
    let mut format = String::new();

    for line in out.lines() {
        let xline = line.trim();
        if xline.starts_with("DLL Name:") {
            if let Some(dll) = xline.split(": ").nth(1) {
                if !ignore.contains(&dll.to_lowercase().as_ref()) {
                    deps.insert(String::from(dll));
                }
            }
        } else if xline.contains(file) {
            format = xline.split(':')
                .nth(1).unwrap_or("").trim()
                .split_whitespace().last().unwrap_or("").to_owned();
        }
    }

    Ok((format,deps))
}

<commit_msg>Fix objdump format parsing, accept lines with ':'<commit_after>
use std::collections::HashSet;
use std::process::Command;

/// Find dependencies for the given exe/dll
///
/// ignore is a list of dll names that should not
/// be added to deps.
///
/// Returns (format, deps) format is the binary format
/// (e.g. pei-x86-64) deps is a set of dependency dlls.
///
/// Internally this calls objdump -p <file>
///
pub fn deps(file: &str, ignore: &[&str]) -> Result<(String,HashSet<String>),String> {

    let command = Command::new("objdump")
        .arg("-p")
        .arg(file)
        .output().unwrap_or_else(|e| {panic!("Failed to execute objdump: {}", e)});

    if !command.status.success() {
        return Err(format!("Error executing objdump({}): {}",
                 command.status.code().unwrap_or(-1),
                 String::from_utf8_lossy(&command.stderr)));
    }

    let mut deps = HashSet::new();
    let out = String::from_utf8_lossy(&command.stdout);
    let mut format = String::new();

    for line in out.lines() {
        let xline = line.trim();
        if xline.starts_with("DLL Name:") {
            if let Some(dll) = xline.split(": ").nth(1) {
                if !ignore.contains(&dll.to_lowercase().as_ref()) {
                    deps.insert(String::from(dll));
                }
            }
        } else if xline.contains(file) {
            format = xline.split(':')
                .last().unwrap_or("").trim()
                .split_whitespace().last().unwrap_or("").to_owned();
        }
    }

    Ok((format,deps))
}

<|endoftext|>


<commit_before>use std::env;
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs;

use http::app::App;
use common::error::Error;

pub struct AppManager {
    next_port: u16,
    pub app_home: String,
    apps: HashMap<String, App>,
}

impl AppManager {
    pub fn new() -> AppManager {
        let app_home = env::var("ZAS_APP_HOME").unwrap_or(AppManager::default_app_home());

        AppManager {
            next_port: 12050,
            app_home: app_home,
            apps: HashMap::new(),
        }
    }

    fn default_app_home() -> String {
        env::home_dir().unwrap().to_str().unwrap().to_string()
    }

    fn start_app(&mut self, app_name: &str) {
        let next_port = self.next_port;
        let app = App::new(&app_name, next_port, &self.app_home);
        self.apps.insert(app_name.to_string(), app);
        self.next_port = next_port + 1;
    }

    pub fn ensure_app_running(&mut self, app_name: &str) -> Result<u16, Error> {
        if !self.apps.contains_key(app_name) {
            let mut path_buf = PathBuf::from(&self.app_home);
            path_buf.push(&app_name);

            if fs::metadata(path_buf.as_path()).is_err() {
                return Err(Error::AppNotConfigured);
            }

            self.start_app(app_name);
        }

        let app = self.apps.get(app_name).unwrap();

        Ok(app.port)
    }
}
<commit_msg>fix: Make default ZAS_APP_HOME be $HOME/.zas<commit_after>use std::env;
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs;

use http::app::App;
use common::error::Error;

pub struct AppManager {
    next_port: u16,
    pub app_home: String,
    apps: HashMap<String, App>,
}

impl AppManager {
    pub fn new() -> AppManager {
        let app_home = env::var("ZAS_APP_HOME").unwrap_or(AppManager::default_app_home());

        AppManager {
            next_port: 12050,
            app_home: app_home,
            apps: HashMap::new(),
        }
    }

    fn default_app_home() -> String {
        let mut path_buf = PathBuf::from(env::home_dir().unwrap().to_str().unwrap());
        path_buf.push(".zas");

        path_buf.to_str().unwrap().to_string()
    }

    fn start_app(&mut self, app_name: &str) {
        let next_port = self.next_port;
        let app = App::new(&app_name, next_port, &self.app_home);
        self.apps.insert(app_name.to_string(), app);
        self.next_port = next_port + 1;
    }

    pub fn ensure_app_running(&mut self, app_name: &str) -> Result<u16, Error> {
        if !self.apps.contains_key(app_name) {
            let mut path_buf = PathBuf::from(&self.app_home);
            path_buf.push(&app_name);

            if fs::metadata(path_buf.as_path()).is_err() {
                return Err(Error::AppNotConfigured);
            }

            self.start_app(app_name);
        }

        let app = self.apps.get(app_name).unwrap();

        Ok(app.port)
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::thread;
use std::collections::HashMap;
use std::process::Command;

use http::app::App;

const DEFAULT_APP_HOME: &'static str = "~/.zas";

pub struct AppManager {
    pub next_port: u16,
    app_home: String,
    pub apps: HashMap<String, App>,
}

impl AppManager {
    pub fn new() -> AppManager {
        let app_home = env::var("ZAS_HOME").unwrap_or(DEFAULT_APP_HOME.to_string());

        AppManager {
            next_port: 12050,
            app_home: app_home,
            apps: HashMap::new(),
        }
    }

    pub fn ensure_app_running(&mut self, app_name: &str) -> u16 {
        if !self.apps.contains_key(app_name) {
            let next_port = self.next_port;
            self.apps.insert(app_name.to_string(), App::new(&app_name, next_port, &self.app_home));
            self.next_port = next_port + 1;

            sleep_until_port_open(next_port);
        }

        let app = self.apps.get(app_name).unwrap();

        app.port
    }
}

fn sleep_until_port_open(port: u16) {
    while !Command::new("nc").arg("-z").arg("localhost").arg(format!("{}", port)).status().unwrap().success() {
        thread::sleep_ms(300);
    }
}
<commit_msg>refactor: Reduce visibility of AppManager fields<commit_after>use std::env;
use std::thread;
use std::collections::HashMap;
use std::process::Command;

use http::app::App;

const DEFAULT_APP_HOME: &'static str = "~/.zas";

pub struct AppManager {
    next_port: u16,
    app_home: String,
    apps: HashMap<String, App>,
}

impl AppManager {
    pub fn new() -> AppManager {
        let app_home = env::var("ZAS_HOME").unwrap_or(DEFAULT_APP_HOME.to_string());

        AppManager {
            next_port: 12050,
            app_home: app_home,
            apps: HashMap::new(),
        }
    }

    pub fn ensure_app_running(&mut self, app_name: &str) -> u16 {
        if !self.apps.contains_key(app_name) {
            let next_port = self.next_port;
            self.apps.insert(app_name.to_string(), App::new(&app_name, next_port, &self.app_home));
            self.next_port = next_port + 1;

            sleep_until_port_open(next_port);
        }

        let app = self.apps.get(app_name).unwrap();

        app.port
    }
}

fn sleep_until_port_open(port: u16) {
    while !Command::new("nc").arg("-z").arg("localhost").arg(format!("{}", port)).status().unwrap().success() {
        thread::sleep_ms(300);
    }
}
<|endoftext|>


<commit_before>use luminance::{Dim2, Equation, Factor, Flat, Mode, Pipe, RenderCommand, ShadingCommand, Tess, TessRender, TessVertices, Uniform, Unit, Vertex, VertexFormat};
use std::cell::RefCell;

use compositing;
use resource::Res;
use scene::Scene;
use shader::Program;
use text::TextTexture;
use texture::{RGBA32F, Texture};

/// Vertex used in overlay’s objects. Position coordinates are in *window space*.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Vert {
  /// Position.
  pub pos: [f32; 3],
  /// Color.
  pub color: [f32; 4]
}

impl Vert {
  pub fn new(pos: [f32; 3], color: [f32; 4]) -> Self {
    Vert {
      pos: pos,
      color: color
    }
  }

  /// Convert the vertex’ position to *clip space*.
  ///
  /// > Note: the depth coordinate is not affected.
  fn to_clip_space(&self, converter: &UnitConverter) -> Self {
    let converted = converter.from_win_coord(self.pos[0], self.pos[1]);
    let pos_ = [converted[0], converted[1], self.pos[2]];

    Vert {
      pos: pos_,
      color: self.color
    }
  }
}

impl Vertex for Vert {
  fn vertex_format() -> VertexFormat {
    <([f32; 3], [f32; 4]) as Vertex>::vertex_format()
  }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Triangle(pub Vert, pub Vert, pub Vert);

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Quad(pub Vert, pub Vert, pub Vert, pub Vert);

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Disc {
  center: Vert,
  radius: f32
}

impl Disc {
  pub fn new(center: Vert, radius: f32) -> Self {
    Disc {
      center: center,
      radius: radius
    }
  }

  fn to_clip_space(&self, converter: &UnitConverter) -> Self {
    Disc {
      center: self.center.to_clip_space(converter),
      radius: self.radius * converter.rw
    }
  }
}

impl Vertex for Disc {
  fn vertex_format() -> VertexFormat {
    <(Vert, f32) as Vertex>::vertex_format()
  }
}

pub type Texture2D = Texture<Flat, Dim2, RGBA32F>;

#[derive(Copy, Clone)]
pub struct Text<'a> {
  text_texture: &'a TextTexture,
  left_lower: Vert
}

impl<'a> Text<'a> {
  pub fn new(text_texture: &'a TextTexture, left_lower: Vert) -> Self {
    Text {
      text_texture: text_texture,
      left_lower: left_lower
    }
  }
}

const DISC_SCREEN_RATIO: &'static Uniform<f32> = &Uniform::new(0);

const TEXT_SAMPLER: &'static Uniform<Unit> = &Uniform::new(0);
const TEXT_POS: &'static Uniform<[f32; 3]> = &Uniform::new(1);
const TEXT_SIZE: &'static Uniform<[f32; 2]> = &Uniform::new(2);
const TEXT_SCALE: &'static Uniform<f32> = &Uniform::new(3);
const TEXT_COLOR: &'static Uniform<[f32; 4]> = &Uniform::new(4);

pub struct Overlay {
  ratio: f32,
  tri_program: Res<Program>,
  tris: RefCell<Tess>,
  disc_program: Res<Program>,
  discs: RefCell<Tess>,
  text_program: Res<Program>,
  text_quad: Tess,
  unit_converter: UnitConverter,
}

impl Overlay {
  pub fn new(w: u32, h: u32, max_tris: usize, max_quads: usize, max_discs: usize, scene: &mut Scene) -> Self {
    let tri_program = scene.get("spectra/overlay/triangle.glsl", vec![]).unwrap();
    let tris = Tess::new(Mode::Triangle, TessVertices::Reserve::<Vert>(max_tris * 3 + max_quads * 4), None);

    let disc_program = scene.get("spectra/overlay/disc.glsl", vec![DISC_SCREEN_RATIO.sem("ratio")]).unwrap();
    let discs = Tess::new(Mode::Point, TessVertices::Reserve::<Disc>(max_discs), None);

    let text_program = scene.get("spectra/overlay/text.glsl", vec![
      TEXT_SAMPLER.sem("text_texture"),
      TEXT_POS.sem("pos"),
      TEXT_SIZE.sem("size"),
      TEXT_SCALE.sem("scale"),
      TEXT_COLOR.sem("color")
    ]).unwrap();

    let text_quad = Tess::attributeless(Mode::TriangleStrip, 4);

    Overlay {
      ratio: w as f32 / h as f32,
      tri_program: tri_program,
      tris: RefCell::new(tris),
      disc_program: disc_program,
      discs: RefCell::new(discs),
      text_program: text_program,
      text_quad: text_quad,
      unit_converter: UnitConverter::new(w, h)
    }
  }

  /// Dispatch render input primitives into GPU buffers.
  fn dispatch(&self, input: &RenderInput) -> (usize, usize) {
    let mut tris_ref = self.tris.borrow_mut();
    let mut tris = tris_ref.as_slice_mut().unwrap();
    let mut tri_i = 0;

    let mut discs_ref = self.discs.borrow_mut();
    let mut discs = discs_ref.as_slice_mut().unwrap();
    let mut disc_i = 0;

    for &Triangle(a, b, c) in input.triangles {
      let abc = [a, b, c];

      for &v in &abc {
        tris[tri_i] = v.to_clip_space(&self.unit_converter);
        tri_i += 1;
      }
    }

    for &Quad(a, b, c, d) in input.quads {
      let abcd = [a, b, c, c, b, d];

      for &v in &abcd {
        tris[tri_i] = v.to_clip_space(&self.unit_converter);
        tri_i += 1;
      }
    }

    for disc in input.discs {
      discs[disc_i] = disc.to_clip_space(&self.unit_converter);
      disc_i += 1;
    }

    (tri_i, disc_i)
  }

  pub fn render<F, R>(&self, input: RenderInput, f: F) -> R where F: FnOnce(Render) -> R {
    let (tri_vert_nb, disc_vert_nb) = self.dispatch(&input);

    let tris_ref = self.tris.borrow();
    let tris = TessRender::one_sub(&tris_ref, tri_vert_nb);

    let discs_ref = self.discs.borrow();
    let discs = TessRender::one_sub(&discs_ref, disc_vert_nb);

    let text_quad = TessRender::one_whole(&self.text_quad);
    let text_quad_render = [Pipe::new(text_quad)];

    let tri_program = self.tri_program.borrow();
    let disc_program = self.disc_program.borrow();
    let text_program = self.text_program.borrow();

    // FIXME: no alloc?
    let text_uniforms: Vec<_> = input.texts.map(|(texts, text_scale)| texts.iter().map(|text| {
      let (tex_w, tex_h) = text.text_texture.size();

      [
        TEXT_SAMPLER.alter(Unit::new(0)),
        TEXT_POS.alter(text.left_lower.to_clip_space(&self.unit_converter).pos),
        TEXT_SIZE.alter(self.unit_converter.from_win_dim(tex_w as f32, tex_h as f32)),
        TEXT_SCALE.alter(text_scale),
        TEXT_COLOR.alter(text.left_lower.color),
      ]
    }).collect()).unwrap_or(Vec::new());

    let text_textures: Vec<_> = input.texts.map(|(texts, _)| texts.iter().map(|text| [&***text.text_texture]).collect()).unwrap_or(Vec::new());

    let text_nb = input.texts.map(|(texts, _)| texts.len()).unwrap_or(0);
    let text_render_cmds: Vec<_> = (0..text_nb).map(|i| {
        let blending = (Equation::Additive, Factor::One, Factor::SrcAlphaComplement);
        Pipe::empty()
          .uniforms(&text_uniforms[i])
          .textures(&text_textures[i])
          .unwrap(RenderCommand::new(Some(blending), true, &text_quad_render))
      }).collect();

    let disc_uniforms = [
      DISC_SCREEN_RATIO.alter(self.ratio)
    ];

    let tris = &[Pipe::new(tris)];
    let tris_render_cmds = &[Pipe::new(RenderCommand::new(None, true, tris))];
    let discs = &[Pipe::new(discs)];
    let discs_render_cmds = &[Pipe::new(RenderCommand::new(None, true, discs))];
    let shading_cmds = &[
      Pipe::new(ShadingCommand::new(&tri_program, tris_render_cmds)),
      Pipe::empty().uniforms(&disc_uniforms).unwrap(ShadingCommand::new(&disc_program, discs_render_cmds)),
      Pipe::new(ShadingCommand::new(&text_program, &text_render_cmds))
    ];

    let render = Render::new(shading_cmds);
    f(render)
  }
}

pub struct Render<'a, 'b> where 'b: 'a {
  shading_cmds: &'a [Pipe<'b, ShadingCommand<'b>>]
}

impl<'a, 'b> Render<'a, 'b> where 'b: 'a {
  fn new(shading_cmds: &'a [Pipe<'b, ShadingCommand<'b>>]) -> Self {
    Render {
      shading_cmds: shading_cmds
    }
  }
}

impl<'a, 'b> compositing::Render<'b> for Render<'a, 'b> where 'b: 'a {
  fn push_shading_commands(&self, shading_commands: &mut Vec<Pipe<'b, ShadingCommand<'b>>>) {
    shading_commands.extend_from_slice(&self.shading_cmds);
  }
}

#[derive(Clone)]
pub struct RenderInput<'a, 'b> where 'b: 'a {
  triangles: &'a [Triangle],
  quads: &'a [Quad],
  discs: &'a [Disc],
  texts: Option<(&'a [Text<'b>], f32)>
}

impl<'a, 'b> RenderInput<'a, 'b> where 'b: 'a {
  pub fn new() -> Self {
    RenderInput {
      triangles: &[],
      quads: &[],
      discs: &[],
      texts: None,
    }
  }

  pub fn triangles(self, triangles: &'a [Triangle]) -> Self {
    RenderInput {
      triangles: triangles,
      ..self
    }
  }

  pub fn quads(self, quads: &'a [Quad]) -> Self {
    RenderInput {
      quads: quads,
      ..self
    }
  }

  pub fn discs(self, discs: &'a [Disc]) -> Self {
    RenderInput {
      discs: discs,
      ..self
    }
  }

  pub fn texts(self, texts: &'a [Text<'b>], scale: f32) -> Self {
    RenderInput {
      texts: Some((texts, scale.max(0.))),
      ..self
    }
  }
}

/// A *unit converter* is used to position stuff in overlay in a universal and screen independent
/// way.
///
/// The idea is that you provide should handle *window space* coordinates only. This type will then
/// help compute normalized coordinates the renderer can understand.
///
/// For the record, *window space* coordinates are defined as below – given a viewport size of
/// *w × h*:
///
/// ^ y = h
/// |
/// |
/// |    x = w
/// O------>
///
/// The formula used to convert from *window space* to what we call *clip space* is:
///
/// > x' = 2x / w - 1
/// > y' = 2y / h - 1
#[derive(Copy, Clone, Debug, PartialEq)]
struct UnitConverter {
  rw: f32,
  rh: f32,
  twice_rw: f32,
  twice_rh: f32
}

impl UnitConverter {
  /// Build a unit converter by giving it the size of the viewport.
  pub fn new(w: u32, h: u32) -> Self {
    let rw = 1. / (w as f32);
    let rh = 1. / (h as f32);

    UnitConverter {
      rw: rw,
      rh: rh,
      twice_rw: 2. * rw,
      twice_rh: 2. * rh
    }
  }

  /// Convert from *window space* coordinates to clip space* ones.
  pub fn from_win_coord(&self, x: f32, y: f32) -> [f32; 2] {
    let x_ = x * self.twice_rw - 1.;
    let y_ = y * self.twice_rh - 1.;

    [x_.min(1.).max(-1.), y_.min(1.).max(-1.)]
  }

  /// Convert from *window space* dimensions to clip space* ones.
  pub fn from_win_dim(&self, w: f32, h: f32) -> [f32; 2] {
    let w_ = w * self.rw;
    let h_ = h * self.rh;

    [w_.min(1.).max(-1.), h_.min(1.).max(-1.)]
  }
}
<commit_msg>Doc on Overlay::render.<commit_after>use luminance::{Dim2, Equation, Factor, Flat, Mode, Pipe, RenderCommand, ShadingCommand, Tess, TessRender, TessVertices, Uniform, Unit, Vertex, VertexFormat};
use std::cell::RefCell;

use compositing;
use resource::Res;
use scene::Scene;
use shader::Program;
use text::TextTexture;
use texture::{RGBA32F, Texture};

/// Vertex used in overlay’s objects. Position coordinates are in *window space*.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Vert {
  /// Position.
  pub pos: [f32; 3],
  /// Color.
  pub color: [f32; 4]
}

impl Vert {
  pub fn new(pos: [f32; 3], color: [f32; 4]) -> Self {
    Vert {
      pos: pos,
      color: color
    }
  }

  /// Convert the vertex’ position to *clip space*.
  ///
  /// > Note: the depth coordinate is not affected.
  fn to_clip_space(&self, converter: &UnitConverter) -> Self {
    let converted = converter.from_win_coord(self.pos[0], self.pos[1]);
    let pos_ = [converted[0], converted[1], self.pos[2]];

    Vert {
      pos: pos_,
      color: self.color
    }
  }
}

impl Vertex for Vert {
  fn vertex_format() -> VertexFormat {
    <([f32; 3], [f32; 4]) as Vertex>::vertex_format()
  }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Triangle(pub Vert, pub Vert, pub Vert);

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Quad(pub Vert, pub Vert, pub Vert, pub Vert);

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Disc {
  center: Vert,
  radius: f32
}

impl Disc {
  pub fn new(center: Vert, radius: f32) -> Self {
    Disc {
      center: center,
      radius: radius
    }
  }

  fn to_clip_space(&self, converter: &UnitConverter) -> Self {
    Disc {
      center: self.center.to_clip_space(converter),
      radius: self.radius * converter.rw
    }
  }
}

impl Vertex for Disc {
  fn vertex_format() -> VertexFormat {
    <(Vert, f32) as Vertex>::vertex_format()
  }
}

pub type Texture2D = Texture<Flat, Dim2, RGBA32F>;

#[derive(Copy, Clone)]
pub struct Text<'a> {
  text_texture: &'a TextTexture,
  left_lower: Vert
}

impl<'a> Text<'a> {
  pub fn new(text_texture: &'a TextTexture, left_lower: Vert) -> Self {
    Text {
      text_texture: text_texture,
      left_lower: left_lower
    }
  }
}

const DISC_SCREEN_RATIO: &'static Uniform<f32> = &Uniform::new(0);

const TEXT_SAMPLER: &'static Uniform<Unit> = &Uniform::new(0);
const TEXT_POS: &'static Uniform<[f32; 3]> = &Uniform::new(1);
const TEXT_SIZE: &'static Uniform<[f32; 2]> = &Uniform::new(2);
const TEXT_SCALE: &'static Uniform<f32> = &Uniform::new(3);
const TEXT_COLOR: &'static Uniform<[f32; 4]> = &Uniform::new(4);

pub struct Overlay {
  ratio: f32,
  tri_program: Res<Program>,
  tris: RefCell<Tess>,
  disc_program: Res<Program>,
  discs: RefCell<Tess>,
  text_program: Res<Program>,
  text_quad: Tess,
  unit_converter: UnitConverter,
}

impl Overlay {
  pub fn new(w: u32, h: u32, max_tris: usize, max_quads: usize, max_discs: usize, scene: &mut Scene) -> Self {
    let tri_program = scene.get("spectra/overlay/triangle.glsl", vec![]).unwrap();
    let tris = Tess::new(Mode::Triangle, TessVertices::Reserve::<Vert>(max_tris * 3 + max_quads * 4), None);

    let disc_program = scene.get("spectra/overlay/disc.glsl", vec![DISC_SCREEN_RATIO.sem("ratio")]).unwrap();
    let discs = Tess::new(Mode::Point, TessVertices::Reserve::<Disc>(max_discs), None);

    let text_program = scene.get("spectra/overlay/text.glsl", vec![
      TEXT_SAMPLER.sem("text_texture"),
      TEXT_POS.sem("pos"),
      TEXT_SIZE.sem("size"),
      TEXT_SCALE.sem("scale"),
      TEXT_COLOR.sem("color")
    ]).unwrap();

    let text_quad = Tess::attributeless(Mode::TriangleStrip, 4);

    Overlay {
      ratio: w as f32 / h as f32,
      tri_program: tri_program,
      tris: RefCell::new(tris),
      disc_program: disc_program,
      discs: RefCell::new(discs),
      text_program: text_program,
      text_quad: text_quad,
      unit_converter: UnitConverter::new(w, h)
    }
  }

  /// Dispatch render input primitives into GPU buffers.
  fn dispatch(&self, input: &RenderInput) -> (usize, usize) {
    let mut tris_ref = self.tris.borrow_mut();
    let mut tris = tris_ref.as_slice_mut().unwrap();
    let mut tri_i = 0;

    let mut discs_ref = self.discs.borrow_mut();
    let mut discs = discs_ref.as_slice_mut().unwrap();
    let mut disc_i = 0;

    for &Triangle(a, b, c) in input.triangles {
      let abc = [a, b, c];

      for &v in &abc {
        tris[tri_i] = v.to_clip_space(&self.unit_converter);
        tri_i += 1;
      }
    }

    for &Quad(a, b, c, d) in input.quads {
      let abcd = [a, b, c, c, b, d];

      for &v in &abcd {
        tris[tri_i] = v.to_clip_space(&self.unit_converter);
        tri_i += 1;
      }
    }

    for disc in input.discs {
      discs[disc_i] = disc.to_clip_space(&self.unit_converter);
      disc_i += 1;
    }

    (tri_i, disc_i)
  }

  /// Render the overlay.
  ///
  /// The render is not directly returned but instead is passed to a continuation to work with
  /// (CPS).
  pub fn render<F, R>(&self, input: RenderInput, f: F) -> R where F: FnOnce(Render) -> R {
    let (tri_vert_nb, disc_vert_nb) = self.dispatch(&input);

    let tris_ref = self.tris.borrow();
    let tris = TessRender::one_sub(&tris_ref, tri_vert_nb);

    let discs_ref = self.discs.borrow();
    let discs = TessRender::one_sub(&discs_ref, disc_vert_nb);

    let text_quad = TessRender::one_whole(&self.text_quad);
    let text_quad_render = [Pipe::new(text_quad)];

    let tri_program = self.tri_program.borrow();
    let disc_program = self.disc_program.borrow();
    let text_program = self.text_program.borrow();

    // FIXME: no alloc?
    let text_uniforms: Vec<_> = input.texts.map(|(texts, text_scale)| texts.iter().map(|text| {
      let (tex_w, tex_h) = text.text_texture.size();

      [
        TEXT_SAMPLER.alter(Unit::new(0)),
        TEXT_POS.alter(text.left_lower.to_clip_space(&self.unit_converter).pos),
        TEXT_SIZE.alter(self.unit_converter.from_win_dim(tex_w as f32, tex_h as f32)),
        TEXT_SCALE.alter(text_scale),
        TEXT_COLOR.alter(text.left_lower.color),
      ]
    }).collect()).unwrap_or(Vec::new());

    let text_textures: Vec<_> = input.texts.map(|(texts, _)| texts.iter().map(|text| [&***text.text_texture]).collect()).unwrap_or(Vec::new());

    let text_nb = input.texts.map(|(texts, _)| texts.len()).unwrap_or(0);
    let text_render_cmds: Vec<_> = (0..text_nb).map(|i| {
        let blending = (Equation::Additive, Factor::One, Factor::SrcAlphaComplement);
        Pipe::empty()
          .uniforms(&text_uniforms[i])
          .textures(&text_textures[i])
          .unwrap(RenderCommand::new(Some(blending), true, &text_quad_render))
      }).collect();

    let disc_uniforms = [
      DISC_SCREEN_RATIO.alter(self.ratio)
    ];

    let tris = &[Pipe::new(tris)];
    let tris_render_cmds = &[Pipe::new(RenderCommand::new(None, true, tris))];
    let discs = &[Pipe::new(discs)];
    let discs_render_cmds = &[Pipe::new(RenderCommand::new(None, true, discs))];
    let shading_cmds = &[
      Pipe::new(ShadingCommand::new(&tri_program, tris_render_cmds)),
      Pipe::empty().uniforms(&disc_uniforms).unwrap(ShadingCommand::new(&disc_program, discs_render_cmds)),
      Pipe::new(ShadingCommand::new(&text_program, &text_render_cmds))
    ];

    let render = Render::new(shading_cmds);
    f(render)
  }
}

pub struct Render<'a, 'b> where 'b: 'a {
  shading_cmds: &'a [Pipe<'b, ShadingCommand<'b>>]
}

impl<'a, 'b> Render<'a, 'b> where 'b: 'a {
  fn new(shading_cmds: &'a [Pipe<'b, ShadingCommand<'b>>]) -> Self {
    Render {
      shading_cmds: shading_cmds
    }
  }
}

impl<'a, 'b> compositing::Render<'b> for Render<'a, 'b> where 'b: 'a {
  fn push_shading_commands(&self, shading_commands: &mut Vec<Pipe<'b, ShadingCommand<'b>>>) {
    shading_commands.extend_from_slice(&self.shading_cmds);
  }
}

#[derive(Clone)]
pub struct RenderInput<'a, 'b> where 'b: 'a {
  triangles: &'a [Triangle],
  quads: &'a [Quad],
  discs: &'a [Disc],
  texts: Option<(&'a [Text<'b>], f32)>
}

impl<'a, 'b> RenderInput<'a, 'b> where 'b: 'a {
  pub fn new() -> Self {
    RenderInput {
      triangles: &[],
      quads: &[],
      discs: &[],
      texts: None,
    }
  }

  pub fn triangles(self, triangles: &'a [Triangle]) -> Self {
    RenderInput {
      triangles: triangles,
      ..self
    }
  }

  pub fn quads(self, quads: &'a [Quad]) -> Self {
    RenderInput {
      quads: quads,
      ..self
    }
  }

  pub fn discs(self, discs: &'a [Disc]) -> Self {
    RenderInput {
      discs: discs,
      ..self
    }
  }

  pub fn texts(self, texts: &'a [Text<'b>], scale: f32) -> Self {
    RenderInput {
      texts: Some((texts, scale.max(0.))),
      ..self
    }
  }
}

/// A *unit converter* is used to position stuff in overlay in a universal and screen independent
/// way.
///
/// The idea is that you provide should handle *window space* coordinates only. This type will then
/// help compute normalized coordinates the renderer can understand.
///
/// For the record, *window space* coordinates are defined as below – given a viewport size of
/// *w × h*:
///
/// ^ y = h
/// |
/// |
/// |    x = w
/// O------>
///
/// The formula used to convert from *window space* to what we call *clip space* is:
///
/// > x' = 2x / w - 1
/// > y' = 2y / h - 1
#[derive(Copy, Clone, Debug, PartialEq)]
struct UnitConverter {
  rw: f32,
  rh: f32,
  twice_rw: f32,
  twice_rh: f32
}

impl UnitConverter {
  /// Build a unit converter by giving it the size of the viewport.
  pub fn new(w: u32, h: u32) -> Self {
    let rw = 1. / (w as f32);
    let rh = 1. / (h as f32);

    UnitConverter {
      rw: rw,
      rh: rh,
      twice_rw: 2. * rw,
      twice_rh: 2. * rh
    }
  }

  /// Convert from *window space* coordinates to clip space* ones.
  pub fn from_win_coord(&self, x: f32, y: f32) -> [f32; 2] {
    let x_ = x * self.twice_rw - 1.;
    let y_ = y * self.twice_rh - 1.;

    [x_.min(1.).max(-1.), y_.min(1.).max(-1.)]
  }

  /// Convert from *window space* dimensions to clip space* ones.
  pub fn from_win_dim(&self, w: f32, h: f32) -> [f32; 2] {
    let w_ = w * self.rw;
    let h_ = h * self.rh;

    [w_.min(1.).max(-1.), h_.min(1.).max(-1.)]
  }
}
<|endoftext|>


<commit_before>use std::collections::*;
use std::fs;
use std::io::{Write, BufReader, BufRead};
use data::*;
use error::*;

const CRLF: &'static str = "\r\n";
const SPACE: &'static str = " ";
const SET_PREFIX: &'static str = "$";
const REMOVE_PREFIX: &'static str = "#";

macro_rules! serialize_set_template { () => ("{prefix}{key}{space}{value}") }
macro_rules! serialize_remove_template { () => ("{prefix}{key}{crlf}") }

#[derive(Debug)]
pub enum PersistType {
    Memory,
    File(String),
}

pub trait Persistable {
    fn set(&mut self, String, Data) -> Result<()>;
    fn remove(&mut self, String) -> Result<()>;
    fn load(&mut self) -> Result<BTreeMap<String, Data>>;
    fn clear(&mut self) -> Result<()>;
}

#[derive(Debug)]
pub struct FileStore {
    path: String,
    file: fs::File,
}

impl FileStore {
    pub fn new(path: String) -> Result<FileStore> {
        Ok(FileStore {
            path: path.clone(),
            file: fs::OpenOptions::new().create(true)
                .truncate(true)
                .write(true)
                .read(true)
                .open(path)?,
        })
    }
}

impl FileStore {
    fn extract_set(line: String) -> Result<(String, Data)> {
        let delimiter_index = line.find(SPACE)
            .ok_or(Error::new(ErrorKind::InvalidSerializedString))?;

        let key = String::from(&line[SET_PREFIX.len()..delimiter_index]);
        let value = String::from(&line[delimiter_index + 1..line.len()]);

        Ok((key, Data::try_from(value)?))
    }

    fn extract_remove(line: String) -> Result<String> {
        let delimiter_index = line.find(CRLF)
            .ok_or(Error::new(ErrorKind::InvalidSerializedString))?;

        Ok(String::from(&line[REMOVE_PREFIX.len()..delimiter_index]))
    }
}

impl Persistable for FileStore {
    fn set(&mut self, key: String, data: Data) -> Result<()> {
        Ok(write!(self.file,
                  serialize_set_template!(),
                  prefix = SET_PREFIX,
                  key = key,
                  space = SPACE,
                  value = data.into_string())?)
    }

    fn remove(&mut self, key: String) -> Result<()> {
        Ok(write!(self.file,
                  serialize_remove_template!(),
                  crlf = CRLF,
                  prefix = REMOVE_PREFIX,
                  key = key)?)
    }

    fn load(&mut self) -> Result<BTreeMap<String, Data>> {
        let mut btree: BTreeMap<String, Data> = BTreeMap::new();

        for line in BufReader::new(fs::File::open(&self.path)?).lines() {
            let line = line? + CRLF;
            if line.starts_with(SET_PREFIX) {
                let (key, value) = Self::extract_set(String::from(line))?;
                btree.insert(key, value);
            } else if line.starts_with(REMOVE_PREFIX) {
                btree.remove(&Self::extract_remove(String::from(line))?);
            } else {
                return Err(Error::new(ErrorKind::InvalidSerializedString));
            }
        }

        Ok(btree)
    }

    fn clear(&mut self) -> Result<()> {
        Ok(self.file.set_len(0)?)
    }
}

#[derive(Debug, Default)]
pub struct MemoryStore {}

impl Persistable for MemoryStore {
    fn set(&mut self, _: String, _: Data) -> Result<()> {
        Ok(())
    }

    fn remove(&mut self, _: String) -> Result<()> {
        Ok(())
    }

    fn load(&mut self) -> Result<BTreeMap<String, Data>> {
        Ok(BTreeMap::new())
    }

    fn clear(&mut self) -> Result<()> {
        Ok(())
    }
}

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

    #[test]
    fn test_set() {
        let mut store = MemoryStore::default();
        assert!(store.set(String::from("test"), Data::Int(1)).is_ok());
    }

    #[test]
    fn test_remove() {
        let mut store = MemoryStore::default();
        assert!(store.remove(String::from("test")).is_ok());
    }

    #[test]
    fn test_load() {
        assert!(MemoryStore::default().load().unwrap().is_empty());
    }

    #[test]
    fn test_clear() {
        let mut store = MemoryStore::default();
        assert!(store.clear().is_ok());
    }
}

#[cfg(test)]
mod file_store_tests {
    use super::*;
    use std::env;
    use std::io::Read;

    #[test]
    fn test_extract_set_string() {
        let line = String::from("$key +value\r\n");
        let (key, value) = FileStore::extract_set(line).unwrap();
        assert_eq!("key", key);
        assert_eq!(Data::String(String::from("value")), value);
    }

    #[test]
    fn test_extract_set_int() {
        let line = String::from("$key :888\r\n");
        let (key, value) = FileStore::extract_set(line).unwrap();
        assert_eq!("key", key);
        assert_eq!(Data::Int(888), value);
    }

    #[test]
    fn test_extract_remove() {
        let line = String::from("#key\r\n");
        let key = FileStore::extract_remove(line).unwrap();
        assert_eq!("key", key);
    }

    #[test]
    fn test_new() {
        let mut cdb_path = env::current_dir().unwrap();
        cdb_path.push("tests/test_new.cdb");
        let path = String::from(cdb_path.as_path().to_str().unwrap());
        let mut store = FileStore::new(path).unwrap();
        store.clear().unwrap();
        assert!(store.file.metadata().unwrap().is_file());
    }

    #[test]
    fn test_set() {
        let mut cdb_path = env::current_dir().unwrap();
        cdb_path.push("tests/test_set.cdb");
        let path = String::from(cdb_path.as_path().to_str().unwrap());
        let mut store = FileStore::new(path.clone()).unwrap();
        store.clear().unwrap();

        store.set(String::from("key"), Data::String(String::from("value")))
            .unwrap();
        store.set(String::from("key"), Data::String(String::from("value")))
            .unwrap();

        let mut content = String::new();
        fs::File::open(path).unwrap().read_to_string(&mut content).unwrap();
        assert_eq!("$key +value\r\n$key +value\r\n", content);
        store.clear().unwrap();
    }

    #[test]
    fn test_clear() {
        let mut cdb_path = env::current_dir().unwrap();
        cdb_path.push("tests/test_clear.cdb");
        let path = String::from(cdb_path.as_path().to_str().unwrap());
        let mut store = FileStore::new(path.clone()).unwrap();
        store.set(String::from("key"), Data::String(String::from("value")))
            .unwrap();

        let mut content = String::new();
        fs::File::open(path.clone())
            .unwrap()
            .read_to_string(&mut content)
            .unwrap();
        assert!(content.len() != 0);

        store.clear().unwrap();

        let mut content_after_clear = String::new();
        fs::File::open(path)
            .unwrap()
            .read_to_string(&mut content_after_clear)
            .unwrap();
        assert!(content_after_clear.len() == 0);
    }

    #[test]
    fn test_remove() {
        let mut cdb_path = env::current_dir().unwrap();
        cdb_path.push("tests/test_remove.cdb");
        let path = String::from(cdb_path.as_path().to_str().unwrap());
        let mut store = FileStore::new(path.clone()).unwrap();
        store.clear().unwrap();

        store.remove(String::from("key1")).unwrap();
        store.remove(String::from("key2")).unwrap();

        let mut content = String::new();
        fs::File::open(path).unwrap().read_to_string(&mut content).unwrap();
        assert_eq!("#key1\r\n#key2\r\n", content);
        store.clear().unwrap();
    }

    #[test]
    fn test_load() {
        let mut cdb_path = env::current_dir().unwrap();
        cdb_path.push("tests/test_load.cdb");
        let path = String::from(cdb_path.as_path().to_str().unwrap());
        let mut store = FileStore::new(path.clone()).unwrap();

        write!(store.file, "$key +value\r\n").unwrap();

        let tree = store.load().unwrap();
        assert_eq!(1, tree.len());
        store.clear().unwrap();
    }
}
<commit_msg>test: optimize persist tests<commit_after>use std::collections::*;
use std::fs;
use std::io::{Write, BufReader, BufRead};
use data::*;
use error::*;

const CRLF: &'static str = "\r\n";
const SPACE: &'static str = " ";
const SET_PREFIX: &'static str = "$";
const REMOVE_PREFIX: &'static str = "#";

macro_rules! serialize_set_template { () => ("{prefix}{key}{space}{value}") }
macro_rules! serialize_remove_template { () => ("{prefix}{key}{crlf}") }

#[derive(Debug)]
pub enum PersistType {
    Memory,
    File(String),
}

pub trait Persistable {
    fn set(&mut self, String, Data) -> Result<()>;
    fn remove(&mut self, String) -> Result<()>;
    fn load(&mut self) -> Result<BTreeMap<String, Data>>;
    fn clear(&mut self) -> Result<()>;
}

#[derive(Debug)]
pub struct FileStore {
    path: String,
    file: fs::File,
}

impl FileStore {
    pub fn new(path: String) -> Result<FileStore> {
        Ok(FileStore {
            path: path.clone(),
            file: fs::OpenOptions::new().create(true)
                .truncate(true)
                .write(true)
                .read(true)
                .open(path)?,
        })
    }
}

impl FileStore {
    fn extract_set(line: String) -> Result<(String, Data)> {
        let delimiter_index = line.find(SPACE)
            .ok_or(Error::new(ErrorKind::InvalidSerializedString))?;

        let key = String::from(&line[SET_PREFIX.len()..delimiter_index]);
        let value = String::from(&line[delimiter_index + 1..line.len()]);

        Ok((key, Data::try_from(value)?))
    }

    fn extract_remove(line: String) -> Result<String> {
        let delimiter_index = line.find(CRLF)
            .ok_or(Error::new(ErrorKind::InvalidSerializedString))?;

        Ok(String::from(&line[REMOVE_PREFIX.len()..delimiter_index]))
    }
}

impl Persistable for FileStore {
    fn set(&mut self, key: String, data: Data) -> Result<()> {
        Ok(write!(self.file,
                  serialize_set_template!(),
                  prefix = SET_PREFIX,
                  key = key,
                  space = SPACE,
                  value = data.into_string())?)
    }

    fn remove(&mut self, key: String) -> Result<()> {
        Ok(write!(self.file,
                  serialize_remove_template!(),
                  crlf = CRLF,
                  prefix = REMOVE_PREFIX,
                  key = key)?)
    }

    fn load(&mut self) -> Result<BTreeMap<String, Data>> {
        let mut btree: BTreeMap<String, Data> = BTreeMap::new();

        for line in BufReader::new(fs::File::open(&self.path)?).lines() {
            let line = line? + CRLF;
            if line.starts_with(SET_PREFIX) {
                let (key, value) = Self::extract_set(String::from(line))?;
                btree.insert(key, value);
            } else if line.starts_with(REMOVE_PREFIX) {
                btree.remove(&Self::extract_remove(String::from(line))?);
            } else {
                return Err(Error::new(ErrorKind::InvalidSerializedString));
            }
        }

        Ok(btree)
    }

    fn clear(&mut self) -> Result<()> {
        Ok(self.file.set_len(0)?)
    }
}

#[derive(Debug, Default)]
pub struct MemoryStore {}

impl Persistable for MemoryStore {
    fn set(&mut self, _: String, _: Data) -> Result<()> {
        Ok(())
    }

    fn remove(&mut self, _: String) -> Result<()> {
        Ok(())
    }

    fn load(&mut self) -> Result<BTreeMap<String, Data>> {
        Ok(BTreeMap::new())
    }

    fn clear(&mut self) -> Result<()> {
        Ok(())
    }
}

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

    #[test]
    fn test_set() {
        let mut store = MemoryStore::default();
        assert!(store.set(String::from("test"), Data::Int(1)).is_ok());
    }

    #[test]
    fn test_remove() {
        let mut store = MemoryStore::default();
        assert!(store.remove(String::from("test")).is_ok());
    }

    #[test]
    fn test_load() {
        assert!(MemoryStore::default().load().unwrap().is_empty());
    }

    #[test]
    fn test_clear() {
        let mut store = MemoryStore::default();
        assert!(store.clear().is_ok());
    }
}

#[cfg(test)]
mod file_store_tests {
    use super::*;
    use std::env;
    use std::io::Read;

    fn get_cdb_path(name: &str) -> String {
        let mut cdb_path = env::current_dir().unwrap();
        cdb_path.push(String::from("tests/") + name);
        String::from(cdb_path.as_path().to_str().unwrap())
    }

    #[test]
    fn test_extract_set_string() {
        let line = String::from("$key +value\r\n");
        let (key, value) = FileStore::extract_set(line).unwrap();
        assert_eq!("key", key);
        assert_eq!(Data::String(String::from("value")), value);
    }

    #[test]
    fn test_extract_set_int() {
        let line = String::from("$key :888\r\n");
        let (key, value) = FileStore::extract_set(line).unwrap();
        assert_eq!("key", key);
        assert_eq!(Data::Int(888), value);
    }

    #[test]
    fn test_extract_remove() {
        let line = String::from("#key\r\n");
        let key = FileStore::extract_remove(line).unwrap();
        assert_eq!("key", key);
    }

    #[test]
    fn test_new() {
        let mut store = FileStore::new(get_cdb_path("test_new.cdb")).unwrap();
        store.clear().unwrap();
        assert!(store.file.metadata().unwrap().is_file());
    }

    #[test]
    fn test_set() {
        let mut store = FileStore::new(get_cdb_path("test_set.cdb")).unwrap();
        store.clear().unwrap();

        store.set(String::from("key"), Data::String(String::from("value")))
            .unwrap();
        store.set(String::from("key"), Data::String(String::from("value")))
            .unwrap();

        let mut content = String::new();
        fs::File::open(get_cdb_path("test_set.cdb"))
            .unwrap()
            .read_to_string(&mut content)
            .unwrap();
        assert_eq!("$key +value\r\n$key +value\r\n", content);
        store.clear().unwrap();
    }

    #[test]
    fn test_clear() {
        let mut store = FileStore::new(get_cdb_path("test_clear.cdb")).unwrap();
        store.set(String::from("key"), Data::String(String::from("value")))
            .unwrap();

        let mut content = String::new();
        fs::File::open(get_cdb_path("test_clear.cdb"))
            .unwrap()
            .read_to_string(&mut content)
            .unwrap();
        assert!(content.len() != 0);

        store.clear().unwrap();

        let mut content_after_clear = String::new();
        fs::File::open(get_cdb_path("test_clear.cdb"))
            .unwrap()
            .read_to_string(&mut content_after_clear)
            .unwrap();
        assert!(content_after_clear.len() == 0);
    }

    #[test]
    fn test_remove() {
        let mut store = FileStore::new(get_cdb_path("test_remove.cdb")).unwrap();
        store.clear().unwrap();

        store.remove(String::from("key1")).unwrap();
        store.remove(String::from("key2")).unwrap();

        let mut content = String::new();
        fs::File::open(get_cdb_path("test_remove.cdb"))
            .unwrap()
            .read_to_string(&mut content)
            .unwrap();
        assert_eq!("#key1\r\n#key2\r\n", content);
        store.clear().unwrap();
    }

    #[test]
    fn test_load() {
        let mut store = FileStore::new(get_cdb_path("test_load.cdb")).unwrap();
        write!(store.file, "$key +value\r\n").unwrap();

        let tree = store.load().unwrap();
        assert_eq!(1, tree.len());
        store.clear().unwrap();
    }
}
<|endoftext|>


<commit_before>// Copyright 2013-2014 The Rust Project Developers.
// Copyright 2018 The Uuid Project Developers.
//
// See the COPYRIGHT file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.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.

//! The [`uuid`] prelude.
//!
//! This module contains the most important items of the [`uuid`] crate.
//!
//! To use the prelude, include the following in your crate root:
//!
//! ```rust
//! extern crate uuid;
//! ```
//!
//! and the following in every module:
//!
//! ```rust
//! use uuid::prelude::*;
//! ```
//!
//! # Prelude Contents
//!
//! Currently the prelude reexports the following:
//!
//! [`uuid`]`::{`[`Error`], [`Uuid`], [`Variant`], [`Version`],
//! builder::[`Builder`]`}`: The fundamental types used in [`uuid`] crate.
//!
//! [`uuid`]: ../index.html
//! [`Error`]: ../enum.Error.html
//! [`Uuid`]: ../struct.Uuid.html
//! [`Variant`]: ../enum.Variant.html
//! [`Version`]: ../enum.Version.html
//! [`Builder`]: ../builder/struct.Builder.html
//!
#![cfg_attr(feature = "v1",
doc = "
[`uuid::v1`]`::{`[`ClockSequence`],[`Context`]`}`: The types useful for
handling uuid version 1. Requires feature `v1`.

[`uuid::v1`]: ../v1/index.html
[`Context`]: ../v1/struct.Context.html
[`ClockSequence`]: ../v1/trait.ClockSequence.html")]

pub use super::{Builder, Bytes, Uuid, Variant, Version};
#[cfg(feature = "v1")]
pub use v1::{ClockSequence, Context};
<commit_msg>add Error to prelude attempt 2<commit_after>// Copyright 2013-2014 The Rust Project Developers.
// Copyright 2018 The Uuid Project Developers.
//
// See the COPYRIGHT file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.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.

//! The [`uuid`] prelude.
//!
//! This module contains the most important items of the [`uuid`] crate.
//!
//! To use the prelude, include the following in your crate root:
//!
//! ```rust
//! extern crate uuid;
//! ```
//!
//! and the following in every module:
//!
//! ```rust
//! use uuid::prelude::*;
//! ```
//!
//! # Prelude Contents
//!
//! Currently the prelude reexports the following:
//!
//! [`uuid`]`::{`[`Error`], [`Uuid`], [`Variant`], [`Version`],
//! builder::[`Builder`]`}`: The fundamental types used in [`uuid`] crate.
//!
//! [`uuid`]: ../index.html
//! [`Error`]: ../enum.Error.html
//! [`Uuid`]: ../struct.Uuid.html
//! [`Variant`]: ../enum.Variant.html
//! [`Version`]: ../enum.Version.html
//! [`Builder`]: ../builder/struct.Builder.html
//!
#![cfg_attr(feature = "v1",
doc = "
[`uuid::v1`]`::{`[`ClockSequence`],[`Context`]`}`: The types useful for
handling uuid version 1. Requires feature `v1`.

[`uuid::v1`]: ../v1/index.html
[`Context`]: ../v1/struct.Context.html
[`ClockSequence`]: ../v1/trait.ClockSequence.html")]

pub use super::{Builder, Bytes, Error, Uuid, Variant, Version};
#[cfg(feature = "v1")]
pub use v1::{ClockSequence, Context};
<|endoftext|>


<commit_before>use command::Command;
use vm::VM;

pub struct Program {
    instructions: Vec<Command>,
    instruction_pointer: Option<usize>,
    current_depth: u64,
    pub status: ProgramStatus,
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ProgramStatus {
    Normal,
    Seeking(u64),
}

#[derive(Clone, Copy, Debug)]
pub struct ProgramPatch {
    pub instruction_pointer: usize,
    pub current_depth: u64,
    pub status: ProgramStatus,
}

fn find_by<T, F>(list: &[T], start: Option<usize>, mut predicate: F) -> Option<usize>
where
    F: FnMut(&T) -> bool,
{
    let start = start.unwrap_or(0);
    for (offset, element) in list.iter().enumerate() {
        if predicate(element) {
            return Some(start + offset);
        }
    }

    return None;
}

impl Program {
    pub fn new() -> Program {
        Program {
            instructions: Vec::new(),
            instruction_pointer: None,
            current_depth: 0,
            status: ProgramStatus::Normal,
        }
    }

    pub fn append(&mut self, instructions: &[Command]) {
        self.instructions.extend(instructions.iter().cloned());
        if self.instruction_pointer.is_none() {
            self.instruction_pointer = Some(0);
        }
    }

    pub fn execute(&mut self, vm: &mut VM) {
        match (self.instruction_pointer, self.status) {
            (Some(index), ProgramStatus::Seeking(goal)) if index < self.instructions.len() => {
                let patch = Program::attempt_forward_jump(
                    &self.instructions,
                    index,
                    goal,
                    self.current_depth,
                );
                println!("SEEKING BRANCH\n{:?}\n", patch);
                return self.update(patch).execute(vm);
            }

            (Some(start), ProgramStatus::Normal) if start < self.instructions.len() => {
                for index in start..self.instructions.len() {
                    match self.instructions[index] {
                        Command::JumpBackward => {
                            if vm.cells[vm.data_pointer] == 0 {
                                self.current_depth -= 1;
                                continue;
                            }

                            let patch = Program::backward_jump(
                                &self.instructions,
                                index,
                                self.current_depth,
                            );
                            println!("BACKWARD BRANCH\n{:?}\n", patch);
                            return self.update(patch).execute(vm);
                        }

                        Command::JumpForward => {
                            if vm.cells[vm.data_pointer] != 0 {
                                self.current_depth += 1;
                                continue;
                            }

                            let patch = Program::attempt_forward_jump(
                                &self.instructions,
                                index,
                                self.current_depth,
                                self.current_depth,
                            );
                            println!("FORWARD BRANCH\n{:?}\n", patch);
                            return self.update(patch).execute(vm);
                        }

                        command if start < self.instructions.len() => vm.apply(command),

                        _ => {}
                    }
                }
            }

            _ => {}
        }
    }

    fn update(&mut self, patch: ProgramPatch) -> &mut Self {
        self.instruction_pointer = Some(patch.instruction_pointer);
        self.current_depth = patch.current_depth;
        self.status = patch.status;
        return self;
    }

    fn backward_jump(commands: &Vec<Command>, start: usize, depth: u64) -> ProgramPatch {
        let goal_depth = depth - 1;
        let mut current_depth = depth;
        for index in (0..start).rev() {
            match commands[index] {
                Command::JumpBackward => current_depth += 1,
                Command::JumpForward => current_depth -= 1,
                _ => {}
            }
            if goal_depth == current_depth {
                return ProgramPatch {
                    instruction_pointer: index,
                    status: ProgramStatus::Normal,
                    current_depth,
                };
            }
        }
        panic!("No opening bracket found?");
    }

    fn attempt_forward_jump(
        commands: &Vec<Command>,
        index: usize,
        goal_depth: u64,
        mut current_depth: u64,
    ) -> ProgramPatch {
        let index = find_by(commands, Some(index), |command| {
            match command {
                &Command::JumpBackward => current_depth -= 1,
                &Command::JumpForward => current_depth += 1,
                _ => {}
            };
            return current_depth == goal_depth;
        });

        return match index {
            Some(index) => ProgramPatch {
                instruction_pointer: index,
                status: ProgramStatus::Normal,
                current_depth,
            },

            None => ProgramPatch {
                instruction_pointer: commands.len(),
                status: ProgramStatus::Seeking(goal_depth),
                current_depth,
            },
        };
    }
}
<commit_msg>Add PartialEq to ProgramPatch<commit_after>use command::Command;
use vm::VM;

pub struct Program {
    instructions: Vec<Command>,
    instruction_pointer: Option<usize>,
    current_depth: u64,
    pub status: ProgramStatus,
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ProgramStatus {
    Normal,
    Seeking(u64),
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ProgramPatch {
    pub instruction_pointer: usize,
    pub current_depth: u64,
    pub status: ProgramStatus,
}

fn find_by<T, F>(list: &[T], start: Option<usize>, mut predicate: F) -> Option<usize>
where
    F: FnMut(&T) -> bool,
{
    let start = start.unwrap_or(0);
    for (offset, element) in list.iter().enumerate() {
        if predicate(element) {
            return Some(start + offset);
        }
    }

    return None;
}

impl Program {
    pub fn new() -> Program {
        Program {
            instructions: Vec::new(),
            instruction_pointer: None,
            current_depth: 0,
            status: ProgramStatus::Normal,
        }
    }

    pub fn append(&mut self, instructions: &[Command]) {
        self.instructions.extend(instructions.iter().cloned());
        if self.instruction_pointer.is_none() {
            self.instruction_pointer = Some(0);
        }
    }

    pub fn execute(&mut self, vm: &mut VM) {
        match (self.instruction_pointer, self.status) {
            (Some(index), ProgramStatus::Seeking(goal)) if index < self.instructions.len() => {
                let patch = Program::attempt_forward_jump(
                    &self.instructions,
                    index,
                    goal,
                    self.current_depth,
                );
                println!("SEEKING BRANCH\n{:?}\n", patch);
                return self.update(patch).execute(vm);
            }

            (Some(start), ProgramStatus::Normal) if start < self.instructions.len() => {
                for index in start..self.instructions.len() {
                    match self.instructions[index] {
                        Command::JumpBackward => {
                            if vm.cells[vm.data_pointer] == 0 {
                                self.current_depth -= 1;
                                continue;
                            }

                            let patch = Program::backward_jump(
                                &self.instructions,
                                index,
                                self.current_depth,
                            );
                            println!("BACKWARD BRANCH\n{:?}\n", patch);
                            return self.update(patch).execute(vm);
                        }

                        Command::JumpForward => {
                            if vm.cells[vm.data_pointer] != 0 {
                                self.current_depth += 1;
                                continue;
                            }

                            let patch = Program::attempt_forward_jump(
                                &self.instructions,
                                index,
                                self.current_depth,
                                self.current_depth,
                            );
                            println!("FORWARD BRANCH\n{:?}\n", patch);
                            return self.update(patch).execute(vm);
                        }

                        command if start < self.instructions.len() => vm.apply(command),

                        _ => {}
                    }
                }
            }

            _ => {}
        }
    }

    fn update(&mut self, patch: ProgramPatch) -> &mut Self {
        self.instruction_pointer = Some(patch.instruction_pointer);
        self.current_depth = patch.current_depth;
        self.status = patch.status;
        return self;
    }

    fn backward_jump(commands: &Vec<Command>, start: usize, depth: u64) -> ProgramPatch {
        let goal_depth = depth - 1;
        let mut current_depth = depth;
        for index in (0..start).rev() {
            match commands[index] {
                Command::JumpBackward => current_depth += 1,
                Command::JumpForward => current_depth -= 1,
                _ => {}
            }
            if goal_depth == current_depth {
                return ProgramPatch {
                    instruction_pointer: index,
                    status: ProgramStatus::Normal,
                    current_depth,
                };
            }
        }
        panic!("No opening bracket found?");
    }

    fn attempt_forward_jump(
        commands: &Vec<Command>,
        index: usize,
        goal_depth: u64,
        mut current_depth: u64,
    ) -> ProgramPatch {
        let index = find_by(commands, Some(index), |command| {
            match command {
                &Command::JumpBackward => current_depth -= 1,
                &Command::JumpForward => current_depth += 1,
                _ => {}
            };
            return current_depth == goal_depth;
        });

        return match index {
            Some(index) => ProgramPatch {
                instruction_pointer: index,
                status: ProgramStatus::Normal,
                current_depth,
            },

            None => ProgramPatch {
                instruction_pointer: commands.len(),
                status: ProgramStatus::Seeking(goal_depth),
                current_depth,
            },
        };
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

use config::{Config, ConfigError};
use config::project::ProjectConf;
use language::Language;
use std::{fs, io};
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};

#[derive(Debug)]
pub struct Project {
    pub name: String,
    pub language: &'static Language,
    pub artifact: String,
}

impl <'a>Project {
    pub fn new(current_dir: &mut PathBuf) -> Result<Project, ProjectError<'a>> {
        match Self::get_config(current_dir) {
            Ok(conf) => {
                let project_name = current_dir.iter().last().unwrap().to_str().unwrap();

                let language: &Language;
                match Language::find(&conf.language) {
                    Some(l) => language = l,
                    None => return Err(ProjectError {
                        message: "Unsupported language",
                        root: RootError::None(()),
                    }),
                }

                Ok(Project {
                    name: project_name.to_string(),
                    language: language,
                    artifact: conf.artifact,
                })
            },
            Err(e) => {
                Err(ProjectError {
                    message: "Could not load project.json",
                    root: RootError::ConfigError(e),
                })
            }
        }
    }

    fn get_config(path: &mut PathBuf) -> Result<ProjectConf, ConfigError> {
        path.push("project.json");
        let conf = try!(ProjectConf::load(&path));
        Ok(conf)
    }

    pub fn create(name: &str, lang_name: &str, is_blank: bool) -> Result<(), ProjectError<'a>> {
        // Check that language is valid
        let language: &Language;
        match Language::find(lang_name) {
            Some(l) => language = l,
            None => return Err(ProjectError {
                message: "Unsupported language",
                root: RootError::None(()),
            }),
        }

        let mut project_path = PathBuf::from(name);

        // Make sure folder doesn't already exist
        if fs::metadata(&project_path).is_ok() {
            return Err(ProjectError {
                message: "Project already exists",
                root: RootError::None(()),
            });
        }

        // Clone example project or create empty project folder
        if is_blank {
            if let Some(e) = fs::create_dir(&project_path).err() {
                return Err(ProjectError {
                    message: "Could not create project directory",
                    root: RootError::IoError(e),
                });
            }
        } else {
            Command::new("git")
                .arg("clone")
                .arg(language.example_repo)
                .arg(name)
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .output()
                .unwrap();
        }

        // Create project.json
        let project_conf = ProjectConf::new(lang_name, language.artifact);
        project_path.push("project.json");
        if let Some(e) = ProjectConf::save(&project_conf, &project_path).err() {
            return Err(ProjectError {
                message: "Could not create project.json",
                root: RootError::ConfigError(e),
            });
        }

        Ok(())
    }

    pub fn run(&self, args: &Vec<String>) -> Result<ExitStatus, ProjectError<'a>> {
        let mut cmd: Command;

        if self.language.runtime.is_some() {
            cmd = Command::new(self.language.runtime.unwrap());
            cmd.arg(&self.artifact);
        } else {
            cmd = Command::new(&self.artifact);
        }

        // Stream command pipes to stdout and strerr
        let cmd_result = cmd.args(args)
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn();

        match cmd_result {
            Ok(mut cmd_child) => {
                match cmd_child.wait() {
                    Ok(s) => Ok(s),
                    Err(e) => Err(ProjectError {
                        message: "Error while running project",
                        root: RootError::IoError(e),
                    })
                }
            }
            Err(e) => return Err(ProjectError {
                message: "Could not run project",
                root: RootError::IoError(e),
            }),
        }
    }
}

#[derive(Debug)]
pub struct ProjectError<'a> {
    pub message: &'a str,
    pub root: RootError,
}

#[derive(Debug)]
pub enum RootError {
    None(()),
    ConfigError(ConfigError),
    IoError(io::Error),
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::{Path, PathBuf};
    use std::fs::{create_dir, File, metadata, remove_dir, remove_dir_all};
    use std::io::Write;

    #[test]
    fn test_new_noconf() {
        create_dir("test_new_noconf").unwrap();
        let mut path = PathBuf::from("test_new_noconf");

        let result = Project::new(&mut path);
        println!("{:?}", result);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Could not load project.json");

        remove_dir_all("test_new_noconf").unwrap();
    }

    #[test]
    fn test_new_nolang() {
        create_dir("test_new_nolang").unwrap();
        let mut path = PathBuf::from("test_new_nolang");

        let test_path = Path::new("test_new_nolang/project.json");
        let mut file = File::create(&test_path).unwrap();
        file.write_all("{\"language\":\"NOLANG\",\"artifact\":\"none\"}".as_bytes()).unwrap();

        let result = Project::new(&mut path);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Unsupported language");

        remove_dir_all("test_new_nolang").unwrap();
    }

    #[test]
    fn test_new_ok() {
        create_dir("test_new_ok").unwrap();
        let mut path = PathBuf::from("test_new_ok");

        let test_path = Path::new("test_new_ok/project.json");
        let mut file = File::create(&test_path).unwrap();
        file.write_all("{\"language\":\"php\",\"artifact\":\"index.php\"}".as_bytes()).unwrap();

        let result = Project::new(&mut path);
        assert!(result.is_ok());

        remove_dir_all("test_new_ok").unwrap();
    }

    #[test]
    fn test_create_nolang() {
        let result = Project::create("test_create_nolang", "NOLANG", true);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Unsupported language");
    }

    #[test]
    fn test_create_exists() {
        create_dir("test_create_exists").unwrap();

        let result = Project::create("test_create_exists", "php", true);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Project already exists");

        remove_dir("test_create_exists").unwrap();
    }

    #[test]
    fn test_create_ok() {
        let result = Project::create("test_create_ok", "php", true);
        assert!(result.is_ok());
        assert!(metadata("test_create_ok").is_ok());
        assert!(metadata("test_create_ok/project.json").is_ok());

        remove_dir_all("test_create_ok").unwrap();
    }
}<commit_msg>Fix bug when calling compiled artifact without config path<commit_after>// Copyright 2015 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

use config::{Config, ConfigError};
use config::project::ProjectConf;
use language::Language;
use std::{fs, io};
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};

#[derive(Debug)]
pub struct Project {
    pub name: String,
    pub language: &'static Language,
    pub artifact: String,
}

impl <'a>Project {
    pub fn new(current_dir: &mut PathBuf) -> Result<Project, ProjectError<'a>> {
        match Self::get_config(current_dir) {
            Ok(conf) => {
                let project_name = current_dir.iter().last().unwrap().to_str().unwrap();

                let language: &Language;
                match Language::find(&conf.language) {
                    Some(l) => language = l,
                    None => return Err(ProjectError {
                        message: "Unsupported language",
                        root: RootError::None(()),
                    }),
                }

                let mut artifact = conf.artifact;

                // If we are calling a binary and the user hasn't
                // specified a path to that binary, prepend "./" so
                // that it can be invoked as a command.
                if language.runtime.is_none() && !&artifact.chars().any(|c: char| c == '/') {
                    artifact = format!("./{}", artifact);
                }

                Ok(Project {
                    name: project_name.to_string(),
                    language: language,
                    artifact: artifact,
                })
            },
            Err(e) => {
                Err(ProjectError {
                    message: "Could not load project.json",
                    root: RootError::ConfigError(e),
                })
            }
        }
    }

    fn get_config(path: &mut PathBuf) -> Result<ProjectConf, ConfigError> {
        path.push("project.json");
        let conf = try!(ProjectConf::load(&path));
        Ok(conf)
    }

    pub fn create(name: &str, lang_name: &str, is_blank: bool) -> Result<(), ProjectError<'a>> {
        // Check that language is valid
        let language: &Language;
        match Language::find(lang_name) {
            Some(l) => language = l,
            None => return Err(ProjectError {
                message: "Unsupported language",
                root: RootError::None(()),
            }),
        }

        let mut project_path = PathBuf::from(name);

        // Make sure folder doesn't already exist
        if fs::metadata(&project_path).is_ok() {
            return Err(ProjectError {
                message: "Project already exists",
                root: RootError::None(()),
            });
        }

        // Clone example project or create empty project folder
        if is_blank {
            if let Some(e) = fs::create_dir(&project_path).err() {
                return Err(ProjectError {
                    message: "Could not create project directory",
                    root: RootError::IoError(e),
                });
            }
        } else {
            Command::new("git")
                .arg("clone")
                .arg(language.example_repo)
                .arg(name)
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .output()
                .unwrap();
        }

        // Create project.json
        let project_conf = ProjectConf::new(lang_name, language.artifact);
        project_path.push("project.json");
        if let Some(e) = ProjectConf::save(&project_conf, &project_path).err() {
            return Err(ProjectError {
                message: "Could not create project.json",
                root: RootError::ConfigError(e),
            });
        }

        Ok(())
    }

    pub fn run(&self, args: &Vec<String>) -> Result<ExitStatus, ProjectError<'a>> {
        let mut cmd: Command;

        if self.language.runtime.is_some() {
            cmd = Command::new(self.language.runtime.unwrap());
            cmd.arg(&self.artifact);
        } else {
            cmd = Command::new(&self.artifact);
        }

        // Stream command pipes to stdout and strerr
        let cmd_result = cmd.args(args)
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn();

        match cmd_result {
            Ok(mut cmd_child) => {
                match cmd_child.wait() {
                    Ok(s) => Ok(s),
                    Err(e) => Err(ProjectError {
                        message: "Error while running project",
                        root: RootError::IoError(e),
                    })
                }
            }
            Err(e) => return Err(ProjectError {
                message: "Could not run project artifact",
                root: RootError::IoError(e),
            }),
        }
    }
}

#[derive(Debug)]
pub struct ProjectError<'a> {
    pub message: &'a str,
    pub root: RootError,
}

#[derive(Debug)]
pub enum RootError {
    None(()),
    ConfigError(ConfigError),
    IoError(io::Error),
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::{Path, PathBuf};
    use std::fs::{create_dir, File, metadata, remove_dir, remove_dir_all};
    use std::io::Write;

    #[test]
    fn test_new_noconf() {
        create_dir("test_new_noconf").unwrap();
        let mut path = PathBuf::from("test_new_noconf");

        let result = Project::new(&mut path);
        println!("{:?}", result);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Could not load project.json");

        remove_dir_all("test_new_noconf").unwrap();
    }

    #[test]
    fn test_new_nolang() {
        create_dir("test_new_nolang").unwrap();
        let mut path = PathBuf::from("test_new_nolang");

        let test_path = Path::new("test_new_nolang/project.json");
        let mut file = File::create(&test_path).unwrap();
        file.write_all("{\"language\":\"NOLANG\",\"artifact\":\"none\"}".as_bytes()).unwrap();

        let result = Project::new(&mut path);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Unsupported language");

        remove_dir_all("test_new_nolang").unwrap();
    }

    #[test]
    fn test_new_ok() {
        create_dir("test_new_ok").unwrap();
        let mut path = PathBuf::from("test_new_ok");

        let test_path = Path::new("test_new_ok/project.json");
        let mut file = File::create(&test_path).unwrap();
        file.write_all("{\"language\":\"php\",\"artifact\":\"index.php\"}".as_bytes()).unwrap();

        let result = Project::new(&mut path);
        assert!(result.is_ok());

        remove_dir_all("test_new_ok").unwrap();
    }

    #[test]
    fn test_create_nolang() {
        let result = Project::create("test_create_nolang", "NOLANG", true);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Unsupported language");
    }

    #[test]
    fn test_create_exists() {
        create_dir("test_create_exists").unwrap();

        let result = Project::create("test_create_exists", "php", true);
        assert!(result.is_err());

        let err = result.err().unwrap();
        assert_eq!(err.message, "Project already exists");

        remove_dir("test_create_exists").unwrap();
    }

    #[test]
    fn test_create_ok() {
        let result = Project::create("test_create_ok", "php", true);
        assert!(result.is_ok());
        assert!(metadata("test_create_ok").is_ok());
        assert!(metadata("test_create_ok/project.json").is_ok());

        remove_dir_all("test_create_ok").unwrap();
    }
}<|endoftext|>


<commit_before>//! A conductor project.

use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;

use dir;
use overrides::Override;
use pod::Pod;
use util::{ConductorPathExt, Error, ToStrOrErr};

/// A `conductor` project, which is represented as a directory containing a
/// `pods` subdirectory.
#[derive(Debug)]
pub struct Project {
    /// The directory which contains our `project`.  Must have a
    /// subdirectory named `pods`.
    root_dir: PathBuf,

    /// The directory to which we'll write our transformed pods.  Defaults
    /// to `root_dir.join(".conductor")`.
    output_dir: PathBuf,

    /// All the pods associated with this project.
    pods: Vec<Pod>,

    /// All the overrides associated with this project.
    overrides: Vec<Override>,
}

impl Project {
    /// Create a `Project` using the current directory as input and the
    /// `.conductor` subdirectory as output.
    ///
    /// ```
    /// use conductor::Project;
    /// use std::env;
    ///
    /// let saved = env::current_dir().unwrap();
    /// env::set_current_dir("examples/hello/pods").unwrap();
    ///
    /// let proj = Project::from_current_dir().unwrap();
    /// assert_eq!(proj.root_dir(), saved.join("examples/hello"));
    /// assert_eq!(proj.output_dir(), saved.join("examples/hello/.conductor"));
    ///
    /// env::set_current_dir(saved).unwrap();
    /// ```
    pub fn from_current_dir() -> Result<Project, Error> {
        // (We can only test this using a doc test because testing it
        // requires messing with `set_current_dir`, which isn't thread safe
        // and will break parallel tests.)
        let current = try!(env::current_dir());
        let root_dir = try!(dir::find_project(&current));
        Ok(Project {
            root_dir: root_dir.clone(),
            output_dir: root_dir.join(".conductor"),
            pods: try!(Project::find_pods(&root_dir)),
            overrides: try!(Project::find_overrides(&root_dir)),
        })
    }

    /// (Tests only.) Create a `Project` from a subirectory of `examples`,
    /// with an output directory under `target/test_output/$NAME`.
    #[cfg(test)]
    pub fn from_example(name: &str) -> Result<Project, Error> {
        use rand::random;
        let example_dir = Path::new("examples").join(name);
        let root_dir = try!(dir::find_project(&example_dir));
        let rand_name = format!("{}-{}", name, random::<u16>());
        Ok(Project {
            root_dir: root_dir.clone(),
            output_dir: Path::new("target/test_output").join(&rand_name),
            pods: try!(Project::find_pods(&root_dir)),
            overrides: try!(Project::find_overrides(&root_dir)),
        })
    }

    /// (Tests only.) Remove our output directory after a test.
    #[cfg(test)]
    pub fn remove_test_output(&self) -> Result<(), Error> {
        if self.output_dir.exists() {
            try!(fs::remove_dir_all(&self.output_dir));
        }
        Ok(())
    }

    /// Find all the pods defined in this project.
    fn find_pods(root_dir: &Path) -> Result<Vec<Pod>, Error> {
        let pods_dir = root_dir.join("pods");
        let mut pods = vec!();
        for glob_result in try!(pods_dir.glob("*.yml")) {
            let path = try!(glob_result);
            // It's safe to unwrap the file_stem because we know it matched
            // our glob.
            let name =
                try!(path.file_stem().unwrap().to_str_or_err()).to_owned();
            pods.push(Pod::new(pods_dir.clone(), name));
        }
        Ok(pods)
    }

    /// Find all the overrides defined in this project.
    fn find_overrides(root_dir: &Path) -> Result<Vec<Override>, Error> {
        let overrides_dir = root_dir.join("pods/overrides");
        let mut overrides = vec!();
        for glob_result in try!(overrides_dir.glob("*")) {
            let path = try!(glob_result);
            if path.is_dir() {
                // It's safe to unwrap file_name because we know it matched
                // our glob.
                let name =
                    try!(path.file_name().unwrap().to_str_or_err()).to_owned();
                overrides.push(Override::new(name));
            }
        }
        Ok(overrides)
    }

    /// The root directory of this project.
    pub fn root_dir(&self) -> &Path {
        &self.root_dir
    }

    /// The output directory of this project.  Normally `.conductor` inside
    /// the `root_dir`, but it may be overriden.
    pub fn output_dir(&self) -> &Path {
        &self.output_dir
    }

    /// Delete our existing output and replace it with a processed and
    /// expanded version of our pod definitions.
    pub fn output(&self) -> Result<(), Error> {
        // Get a path to our input pods directory.
        let in_pods = self.root_dir.join("pods");

        // Get a path to our output pods directory (and delete it if it
        // exists).
        let out_pods = self.output_dir.join("pods");
        if out_pods.exists() {
            try!(fs::remove_dir_all(&out_pods));
        }

        // Iterate over our *.env files recursively.
        for glob_result in try!(in_pods.glob("**/*.env")) {
            let rel = try!(try!(glob_result).strip_prefix(&in_pods)).to_owned();
            let in_path = in_pods.join(&rel);
            let out_path = try!(out_pods.join(&rel).with_guaranteed_parent());
            debug!("Copy {} to {}", in_path.display(), out_path.display());
            try!(fs::copy(in_path, out_path));
        }

        // Copy over our top-level pods.
        for pod in &self.pods {
            let rel = pod.rel_path();
            let out_path = try!(out_pods.join(&rel).with_guaranteed_parent());
            debug!("Generating {}", out_path.display());

            let file = try!(pod.read());
            try!(file.write_to_path(out_path));

            // Copy over any override pods, too.
            for ovr in &self.overrides {
                let rel = pod.override_rel_path(ovr);
                let out_path = try!(out_pods.join(&rel).with_guaranteed_parent());
                debug!("Generating {}", out_path.display());

                let file = try!(pod.read_override(ovr));
                try!(file.write_to_path(out_path));
            }
        }

        Ok(())
    }
}

#[test]
fn new_from_example_uses_example_and_target() {
    let proj = Project::from_example("hello").unwrap();
    assert_eq!(proj.root_dir, Path::new("examples/hello"));
    let output_dir = proj.output_dir.to_str_or_err().unwrap();
    assert!(output_dir.starts_with("target/test_output/hello-"));
}

#[test]
fn output_copies_env_files() {
    let proj = Project::from_example("hello").unwrap();
    proj.output().unwrap();
    assert!(proj.output_dir.join("pods/common.env").exists());
    assert!(proj.output_dir.join("pods/overrides/test/common.env").exists());
}

#[test]
fn output_processes_pods_and_overrides() {
    //use docker_compose::v2 as dc;

    let proj = Project::from_example("hello").unwrap();
    proj.output().unwrap();
    assert!(proj.output_dir.join("pods/frontend.yml").exists());
    assert!(proj.output_dir.join("pods/overrides/production/frontend.yml").exists());
    assert!(proj.output_dir.join("pods/overrides/test/frontend.yml").exists());

    //dc::File::
}

#[test]
fn pods_are_loaded() {
    let proj = Project::from_example("hello").unwrap();
    let names: Vec<_> = proj.pods.iter().map(|pod| pod.name()).collect();
    assert_eq!(names, ["frontend"]);
}

#[test]
fn overrides_are_loaded() {
    let proj = Project::from_example("hello").unwrap();
    let names: Vec<_> = proj.overrides.iter().map(|o| o.name()).collect();
    assert_eq!(names, ["development", "production", "test"]);
}
<commit_msg>Delete test output after successful tests<commit_after>//! A conductor project.

use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;

use dir;
use overrides::Override;
use pod::Pod;
use util::{ConductorPathExt, Error, ToStrOrErr};

/// A `conductor` project, which is represented as a directory containing a
/// `pods` subdirectory.
#[derive(Debug)]
pub struct Project {
    /// The directory which contains our `project`.  Must have a
    /// subdirectory named `pods`.
    root_dir: PathBuf,

    /// The directory to which we'll write our transformed pods.  Defaults
    /// to `root_dir.join(".conductor")`.
    output_dir: PathBuf,

    /// All the pods associated with this project.
    pods: Vec<Pod>,

    /// All the overrides associated with this project.
    overrides: Vec<Override>,
}

impl Project {
    /// Create a `Project` using the current directory as input and the
    /// `.conductor` subdirectory as output.
    ///
    /// ```
    /// use conductor::Project;
    /// use std::env;
    ///
    /// let saved = env::current_dir().unwrap();
    /// env::set_current_dir("examples/hello/pods").unwrap();
    ///
    /// let proj = Project::from_current_dir().unwrap();
    /// assert_eq!(proj.root_dir(), saved.join("examples/hello"));
    /// assert_eq!(proj.output_dir(), saved.join("examples/hello/.conductor"));
    ///
    /// env::set_current_dir(saved).unwrap();
    /// ```
    pub fn from_current_dir() -> Result<Project, Error> {
        // (We can only test this using a doc test because testing it
        // requires messing with `set_current_dir`, which isn't thread safe
        // and will break parallel tests.)
        let current = try!(env::current_dir());
        let root_dir = try!(dir::find_project(&current));
        Ok(Project {
            root_dir: root_dir.clone(),
            output_dir: root_dir.join(".conductor"),
            pods: try!(Project::find_pods(&root_dir)),
            overrides: try!(Project::find_overrides(&root_dir)),
        })
    }

    /// (Tests only.) Create a `Project` from a subirectory of `examples`,
    /// with an output directory under `target/test_output/$NAME`.
    #[cfg(test)]
    pub fn from_example(name: &str) -> Result<Project, Error> {
        use rand::random;
        let example_dir = Path::new("examples").join(name);
        let root_dir = try!(dir::find_project(&example_dir));
        let rand_name = format!("{}-{}", name, random::<u16>());
        Ok(Project {
            root_dir: root_dir.clone(),
            output_dir: Path::new("target/test_output").join(&rand_name),
            pods: try!(Project::find_pods(&root_dir)),
            overrides: try!(Project::find_overrides(&root_dir)),
        })
    }

    /// (Tests only.) Remove our output directory after a test.
    #[cfg(test)]
    pub fn remove_test_output(&self) -> Result<(), Error> {
        if self.output_dir.exists() {
            try!(fs::remove_dir_all(&self.output_dir));
        }
        Ok(())
    }

    /// Find all the pods defined in this project.
    fn find_pods(root_dir: &Path) -> Result<Vec<Pod>, Error> {
        let pods_dir = root_dir.join("pods");
        let mut pods = vec!();
        for glob_result in try!(pods_dir.glob("*.yml")) {
            let path = try!(glob_result);
            // It's safe to unwrap the file_stem because we know it matched
            // our glob.
            let name =
                try!(path.file_stem().unwrap().to_str_or_err()).to_owned();
            pods.push(Pod::new(pods_dir.clone(), name));
        }
        Ok(pods)
    }

    /// Find all the overrides defined in this project.
    fn find_overrides(root_dir: &Path) -> Result<Vec<Override>, Error> {
        let overrides_dir = root_dir.join("pods/overrides");
        let mut overrides = vec!();
        for glob_result in try!(overrides_dir.glob("*")) {
            let path = try!(glob_result);
            if path.is_dir() {
                // It's safe to unwrap file_name because we know it matched
                // our glob.
                let name =
                    try!(path.file_name().unwrap().to_str_or_err()).to_owned();
                overrides.push(Override::new(name));
            }
        }
        Ok(overrides)
    }

    /// The root directory of this project.
    pub fn root_dir(&self) -> &Path {
        &self.root_dir
    }

    /// The output directory of this project.  Normally `.conductor` inside
    /// the `root_dir`, but it may be overriden.
    pub fn output_dir(&self) -> &Path {
        &self.output_dir
    }

    /// Delete our existing output and replace it with a processed and
    /// expanded version of our pod definitions.
    pub fn output(&self) -> Result<(), Error> {
        // Get a path to our input pods directory.
        let in_pods = self.root_dir.join("pods");

        // Get a path to our output pods directory (and delete it if it
        // exists).
        let out_pods = self.output_dir.join("pods");
        if out_pods.exists() {
            try!(fs::remove_dir_all(&out_pods));
        }

        // Iterate over our *.env files recursively.
        for glob_result in try!(in_pods.glob("**/*.env")) {
            let rel = try!(try!(glob_result).strip_prefix(&in_pods)).to_owned();
            let in_path = in_pods.join(&rel);
            let out_path = try!(out_pods.join(&rel).with_guaranteed_parent());
            debug!("Copy {} to {}", in_path.display(), out_path.display());
            try!(fs::copy(in_path, out_path));
        }

        // Copy over our top-level pods.
        for pod in &self.pods {
            let rel = pod.rel_path();
            let out_path = try!(out_pods.join(&rel).with_guaranteed_parent());
            debug!("Generating {}", out_path.display());

            let file = try!(pod.read());
            try!(file.write_to_path(out_path));

            // Copy over any override pods, too.
            for ovr in &self.overrides {
                let rel = pod.override_rel_path(ovr);
                let out_path = try!(out_pods.join(&rel).with_guaranteed_parent());
                debug!("Generating {}", out_path.display());

                let file = try!(pod.read_override(ovr));
                try!(file.write_to_path(out_path));
            }
        }

        Ok(())
    }
}

#[test]
fn new_from_example_uses_example_and_target() {
    let proj = Project::from_example("hello").unwrap();
    assert_eq!(proj.root_dir, Path::new("examples/hello"));
    let output_dir = proj.output_dir.to_str_or_err().unwrap();
    assert!(output_dir.starts_with("target/test_output/hello-"));
    proj.remove_test_output().unwrap();
}

#[test]
fn output_copies_env_files() {
    let proj = Project::from_example("hello").unwrap();
    proj.output().unwrap();
    assert!(proj.output_dir.join("pods/common.env").exists());
    assert!(proj.output_dir.join("pods/overrides/test/common.env").exists());
    proj.remove_test_output().unwrap();
}

#[test]
fn output_processes_pods_and_overrides() {
    //use docker_compose::v2 as dc;

    let proj = Project::from_example("hello").unwrap();
    proj.output().unwrap();
    assert!(proj.output_dir.join("pods/frontend.yml").exists());
    assert!(proj.output_dir.join("pods/overrides/production/frontend.yml").exists());
    assert!(proj.output_dir.join("pods/overrides/test/frontend.yml").exists());

    //dc::File::

    proj.remove_test_output().unwrap();
}

#[test]
fn pods_are_loaded() {
    let proj = Project::from_example("hello").unwrap();
    let names: Vec<_> = proj.pods.iter().map(|pod| pod.name()).collect();
    assert_eq!(names, ["frontend"]);
    proj.remove_test_output().unwrap();
}

#[test]
fn overrides_are_loaded() {
    let proj = Project::from_example("hello").unwrap();
    let names: Vec<_> = proj.overrides.iter().map(|o| o.name()).collect();
    assert_eq!(names, ["development", "production", "test"]);
    proj.remove_test_output().unwrap();
}
<|endoftext|>