<commit_before>use error::Error;
use stdlib::Stdlib;
use to_javascript::ToJavaScript;

#[derive(Debug, PartialEq)]
pub struct NumberExpression {
    pub value: f64,
}

impl NumberExpression {
    pub fn new(value: f64) -> NumberExpression {
        NumberExpression { value }
    }
}

impl ToJavaScript for NumberExpression {
    fn eval(&mut self, _stdlib: &mut Stdlib) -> Result<String, Error> {
        Ok(format!("{}", self.value))
    }
}

#[derive(Debug, PartialEq)]
pub struct IdentifierExpression {
    pub value: String,
}

impl IdentifierExpression {
    pub fn new(value: String) -> IdentifierExpression {
        IdentifierExpression { value }
    }
}

impl ToJavaScript for IdentifierExpression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        if let Some(_) = stdlib.variable_table.get(&self.value) {
            // TODO: Remove clone here
            Ok(self.value.clone())
        } else if let Some(_) = stdlib.function_table.get(&self.value) {
            Ok(self.value.clone())
        } else {
            // TODO: Remove clone here
            Err(Error::undefined_var(self.value.clone()))
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct BooleanExpression {
    pub value: bool,
}

impl BooleanExpression {
    pub fn new(value: bool) -> BooleanExpression {
        BooleanExpression { value }
    }
}

impl ToJavaScript for BooleanExpression {
    fn eval(&mut self, _stdlib: &mut Stdlib) -> Result<String, Error> {
        Ok(self.value.to_string())
    }
}

#[derive(Debug, PartialEq)]
pub struct StringExpression {
    pub value: String,
}

impl StringExpression {
    pub fn new(value: String) -> StringExpression {
        StringExpression { value }
    }
}

impl ToJavaScript for StringExpression {
    fn eval(&mut self, _stdlib: &mut Stdlib) -> Result<String, Error> {
        // TODO: Remove this clone
        Ok(self.value.clone())
    }
}

#[derive(Debug, PartialEq)]
pub struct ListExpression {
    pub value: Vec<Box<Expression>>,
    qouted: bool,
}

impl ListExpression {
    pub fn new(qouted: bool, value: Vec<Box<Expression>>) -> ListExpression {
        ListExpression { qouted, value }
    }

    pub fn new_quoted(value: Vec<Box<Expression>>) -> ListExpression {
        ListExpression {
            qouted: true,
            value,
        }
    }

    pub fn new_unquoted(value: Vec<Box<Expression>>) -> ListExpression {
        ListExpression {
            qouted: false,
            value,
        }
    }
}

impl ToJavaScript for ListExpression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        // TODO: Remove unwrap here
        let (name, args) = self.value.split_first_mut().unwrap();

        let expr_name = name.eval(stdlib)?;

        if self.qouted {
            stdlib.function_table.get("quote").unwrap()("quote".to_string(), args, stdlib)
        } else {
            match stdlib.function_table.clone().get::<str>(&expr_name) {
                Some(func) => func(expr_name, args, stdlib),
                None => {
                    let args = args.into_iter()
                        .map(|e| e.eval(stdlib).or_else(|e| Err(e)).unwrap())
                        .collect::<Vec<String>>()
                        .join(",");

                    Ok(format!("({}({}))", expr_name, args))
                }
            }

            // TODO: Revise this code
            /*match args[1] {
                box Expression::Identifier(ref mut ident) =>
                    if let Some(func) = stdlib.function_table.clone().get::<str>(&ident.value) {
                        func(expr_name, args, stdlib)
                    },

                _ => {
                        let args = args.into_iter()
                            .map(|e| e.eval(stdlib).or_else(|e| Err(e)).unwrap())
                            .collect::<Vec<String>>()
                            .join(",");

                        Ok(format!("({}({}))", expr_name, args))
                    },
            }*/
            // TODO: Remove to_string
            /*match (stdlib.function_table.clone().get::<str>(args[1]), box args[1]) {
                (Some(func), Expression::Identifier(ident)) => func(expr_name.clone(), args, stdlib),
                (_, _) => {
                    let args = args.into_iter()
                        .map(|e| e.eval(stdlib).or_else(|e| Err(e)).unwrap())
                        .collect::<Vec<String>>()
                        .join(",");

                    Ok(format!("({}({}))", expr_name, args))
                }
            }*/        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Expression {
    Number(NumberExpression),
    Identifier(IdentifierExpression),
    Boolean(BooleanExpression),
    String(StringExpression),
    List(ListExpression),
}

impl ToJavaScript for Expression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        match self {
            Expression::Number(expr) => expr.eval(stdlib),
            Expression::Identifier(expr) => expr.eval(stdlib),
            Expression::Boolean(expr) => expr.eval(stdlib),
            Expression::String(expr) => expr.eval(stdlib),
            Expression::List(expr) => expr.eval(stdlib),
        }
    }
}

/*
#[derive(Debug, PartialEq)]
pub enum Expression {
    Number(f64),
    Identifier(String),
    Boolean(bool),
    String(String),
    List(Vec<Box<Expression>>),

    // String = Function Name, Vec<Box<String>> = Argument List, Box<Expression> = Body
    FuncLiteral(String, Vec<String>, Box<Expression>),

    // Box<Expression> = Expression being called, Vec<Box<Expression>> = Arguments
    FuncCall(Box<Expression>, Vec<Box<Expression>>),
}

impl ToJavaScript for Expression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        match *self {
            Expression::Number(ref val) => Ok(format!("{}", val)),
            Expression::Identifier(ref val) => if let Some(_) = stdlib.function_table.get(val) {
                Ok(format!("{}", val))
            } else {
                Err(Error::undefined_var("Add undefined var here"))
            },
            Expression::Boolean(ref val) => Ok(format!("{}", val)),
            Expression::String(ref val) => Ok(val.clone()),

            Expression::List(ref mut vals) => {
                let vals = vals.into_iter()
                    // We can assume the unwrap is just a formality
                    .map(|e| e.eval(stdlib).or_else(|e| Err(e)).unwrap())
                    .collect::<Vec<String>>()
                    .join(",");

                Ok(format!("[{}]", vals))
            }

            Expression::FuncLiteral(ref name, ref args, ref mut body) => {
                let mut stdlib = Stdlib::new(Table::new(Some(Box::new(&stdlib.variable_table))));

                args.into_iter().for_each(|arg| {
                    stdlib
                        .variable_table
                        .insert(arg.to_string(), arg.to_string())
                });

                let args = args
                            .into_iter()
                            // TODO: Remove cloned
                            .cloned()
                            .map(|e| e)
                            .collect::<Vec<String>>()
                            .join(",");

                Ok(format!(
                    "function {} ({}){{ {}; }}",
                    name,
                    args,
                    body.eval(&mut stdlib)?
                ))
            }

            Expression::FuncCall(ref mut name, ref mut args) => {
                // Debox the name from Box<Expression> to Expression
                let &mut box ref mut expr_name = name;

                // TODO: Rethink this code
                match expr_name {
                    Expression::Identifier(ref ident) => {
                        // TODO: Remove clone
                        match stdlib.function_table.clone().get(&ident.clone()) {
                            Some(func) => func(ident.to_string(), args, stdlib),
                            None => {
                                let args = args.into_iter()
                                    .map(|e| e.eval(stdlib).or_else(|e| Err(e)).unwrap())
                                    .collect::<Vec<String>>()
                                    .join(",");

                                Ok(format!("({}({}))", expr_name.eval(stdlib)?, args))
                            }
                        }
                    }

                    _ => unimplemented!(),
                }
            }
        }
    }
}
*/
<commit_msg>Cleanup ast.rs<commit_after>use error::Error;
use stdlib::Stdlib;
use to_javascript::ToJavaScript;

#[derive(Clone, Debug, PartialEq)]
pub struct NumberExpression {
    pub value: f64,
}

impl NumberExpression {
    pub fn new(value: f64) -> NumberExpression {
        NumberExpression { value }
    }
}

impl ToJavaScript for NumberExpression {
    fn eval(&mut self, _stdlib: &mut Stdlib) -> Result<String, Error> {
        Ok(format!("{}", self.value))
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct IdentifierExpression {
    pub value: String,
}

impl IdentifierExpression {
    pub fn new(value: String) -> IdentifierExpression {
        IdentifierExpression { value }
    }
}

impl ToJavaScript for IdentifierExpression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        if let Some(_) = stdlib.variable_table.get(&self.value) {
            // TODO: Remove clone here
            Ok(self.value.clone())
        } else if let Some(_) = stdlib.function_table.get(&self.value) {
            Ok(self.value.clone())
        } else {
            // TODO: Remove clone here
            Err(Error::undefined_var(self.value.clone()))
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct BooleanExpression {
    pub value: bool,
}

impl BooleanExpression {
    pub fn new(value: bool) -> BooleanExpression {
        BooleanExpression { value }
    }
}

impl ToJavaScript for BooleanExpression {
    fn eval(&mut self, _stdlib: &mut Stdlib) -> Result<String, Error> {
        Ok(self.value.to_string())
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct StringExpression {
    pub value: String,
}

impl StringExpression {
    pub fn new(value: String) -> StringExpression {
        StringExpression { value }
    }
}

impl ToJavaScript for StringExpression {
    fn eval(&mut self, _stdlib: &mut Stdlib) -> Result<String, Error> {
        // TODO: Remove this clone
        Ok(self.value.clone())
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct ListExpression {
    pub value: Vec<Box<Expression>>,
    qouted: bool,
}

impl ListExpression {
    pub fn new(qouted: bool, value: Vec<Box<Expression>>) -> ListExpression {
        ListExpression { qouted, value }
    }

    pub fn new_quoted(value: Vec<Box<Expression>>) -> ListExpression {
        ListExpression {
            qouted: true,
            value,
        }
    }

    pub fn new_unquoted(value: Vec<Box<Expression>>) -> ListExpression {
        ListExpression {
            qouted: false,
            value,
        }
    }
}

impl ToJavaScript for ListExpression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        // TODO: Remove unwrap here
        let (name, args) = self.value.split_first_mut().unwrap();

        let expr_name = name.eval(stdlib)?;

        if self.qouted {
            stdlib.function_table.get("quote").unwrap()("quote".to_string(), args, stdlib)
        } else {
            match stdlib.function_table.clone().get::<str>(&expr_name) {
                Some(func) => func(expr_name, args, stdlib),
                None => {
                    let args = args.into_iter()
                        .map(|e| e.eval(stdlib).or_else(|e| Err(e)).unwrap())
                        .collect::<Vec<String>>()
                        .join(",");

                    Ok(format!("({}({}))", expr_name, args))
                }
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Expression {
    Number(NumberExpression),
    Identifier(IdentifierExpression),
    Boolean(BooleanExpression),
    String(StringExpression),
    List(ListExpression),
}

impl ToJavaScript for Expression {
    fn eval(&mut self, stdlib: &mut Stdlib) -> Result<String, Error> {
        match self {
            Expression::Number(expr) => expr.eval(stdlib),
            Expression::Identifier(expr) => expr.eval(stdlib),
            Expression::Boolean(expr) => expr.eval(stdlib),
            Expression::String(expr) => expr.eval(stdlib),
            Expression::List(expr) => expr.eval(stdlib),
        }
    }
}
<|endoftext|>


<commit_before>
use num::*;
use mat::Mat4;
use vec::Vec3;
use std::ops::*;

#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[repr(C)]
pub struct Quaternion<T: Num + Float + Copy> {
    pub x: T,
    pub y: T,
    pub w: T,
    pub z: T,
}

impl<T: Num + Float + Copy> Quaternion<T> {
    /// Creates a new identity quaternion
    pub fn new() -> Quaternion<T> {
        Quaternion {
            x: T::zero(),
            y: T::zero(),
            z: T::zero(),
            w: T::one(),
        }
    }

    /// Creates a quaternion representing a counterclockwise rotation of `angle` radians around the
    /// given axis. This function normalizes the axis, but if the axis is `(0, 0, 0)` the quaternion
    /// will have its `x`, `y` and `z` fields set to 0.
    pub fn rotation(angle: T, axis: Vec3<T>) -> Quaternion<T> {
        let axis = axis.normalize();
        let angle = angle / (T::one() + T::one());
        let (sin, cos) = angle.sin_cos();
        Quaternion {
            x: axis.x*sin,
            y: axis.y*sin,
            z: axis.z*sin,
            w: cos,
        }
    }

    /// Calculates the length of this quaternion, raised to the power of two. Note that this is
    /// cheaper than computing the actual length.
    pub fn len_sqr(&self) -> T {
        self.x*self.x + self.y*self.y + self.z*self.z + self.w*self.w
    }

    /// Calculates the length of this quaternion.
    pub fn len(&self) -> T {
        (self.x*self.x + self.y*self.y + self.z*self.z + self.w*self.w).sqrt()
    }

    /// Normalizes this quaternion, returning a new quaternion with length 1.
    pub fn normalize(&self) -> Quaternion<T> {
        *self / self.len()
    }
}

impl<T: Num + Float + Copy> Mul for Quaternion<T> {
    type Output = Self;
    fn mul(self, other: Quaternion<T>) -> Self {
        Quaternion {
            x: self.w*other.x + self.x*other.w + self.y*other.z - self.z*other.y,
            y: self.w*other.y + self.y*other.w + self.z*other.x - self.x*other.z,
            z: self.w*other.z + self.z*other.w + self.x*other.y - self.y*other.x,
            w: self.w*other.w - self.x*other.x - self.y*other.y - self.z*other.z,
        }
    }
}
impl<T: Num + Float + Copy> MulAssign for Quaternion<T> {
    fn mul_assign(&mut self, other: Quaternion<T>) {
        let x = self.w*other.x + self.x*other.w + self.y*other.z - self.z*other.y;
        let y = self.w*other.y + self.y*other.w + self.z*other.x - self.x*other.z;
        let z = self.w*other.z + self.z*other.w + self.x*other.y - self.y*other.x;
        let w = self.w*other.w - self.x*other.x - self.y*other.y - self.z*other.z;
        self.x = x;
        self.y = y;
        self.z = z;
        self.w = w;
    }
}
impl<T: Num + Float + Copy> Mul<T> for Quaternion<T> {
    type Output = Self;
    fn mul(self, scalar: T) -> Self {
        Quaternion {
            x: self.x * scalar,
            y: self.y * scalar,
            z: self.z * scalar,
            w: self.w * scalar,
        }
    }
}
impl<T: Num + Float + Copy> MulAssign<T> for Quaternion<T> {
    fn mul_assign(&mut self, scalar: T) {
        self.x = self.x * scalar;
        self.y = self.y * scalar;
        self.z = self.z * scalar;
        self.w = self.w * scalar;
    }
}
impl<T: Num + Float + Copy> Div<T> for Quaternion<T> {
    type Output = Self;
    fn div(self, scalar: T) -> Self {
        Quaternion {
            x: self.x / scalar,
            y: self.y / scalar,
            z: self.z / scalar,
            w: self.w / scalar,
        }
    }
}
impl<T: Num + Float + Copy> DivAssign<T> for Quaternion<T> {
    fn div_assign(&mut self, scalar: T) {
        self.x = self.x / scalar;
        self.y = self.y / scalar;
        self.z = self.z / scalar;
        self.w = self.w / scalar;
    }
}

impl<T: Num + Float + Copy> From<Quaternion<T>> for Mat4<T> {
    fn from(quat: Quaternion<T>) -> Mat4<T> {
        Mat4::from_quaternion(quat.x, quat.y, quat.z, quat.w)
    }
}

#[cfg(test)]
mod tests {
    use std::f32;
    use super::*;
    use {Mat4, Vec4};

    #[test]
    fn identity() {
        let identity = Quaternion::<f32>::new();
        assert_eq!(identity, identity*identity);
        assert_eq!(identity, identity*identity*identity);
    }

    #[test]
    fn matrix_rotation() {
        let quat = Quaternion::<f32>::rotation(f32::consts::PI/2.0, Vec3::new(1.0, 0.0, 0.0));
        let mat: Mat4<f32> = quat.into();

        let a = Vec4::new(0.0, 1.0, 0.0, 1.0);
        let b = Vec4::new(0.0, 0.0, 1.0, 1.0);

        let dif = (mat*a - b).len();
        assert!(dif < 0.001);
    }

    #[test]
    fn matrix_rotation_reverse() {
        let mat_a: Mat4<f32> = Quaternion::rotation(f32::consts::PI/2.0, Vec3::new(1.0, 0.0, 0.0)).into();
        let mat_b: Mat4<f32> = Quaternion::rotation(-f32::consts::PI/2.0, Vec3::new(1.0, 0.0, 0.0)).into();

        let a = Vec4::new(0.0, 1.0, 0.0, 1.0);

        let dif = (mat_a*mat_b*a - a).len();
        assert!(dif < 0.001);
    }
}
<commit_msg>Fixed quaternion default<commit_after>
use num::*;
use mat::Mat4;
use vec::Vec3;
use std::ops::*;

#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct Quaternion<T: Num + Float + Copy> {
    pub x: T,
    pub y: T,
    pub w: T,
    pub z: T,
}

impl<T: Num + Float + Copy> Default for Quaternion<T> {
    fn default() -> Quaternion<T> {
        Quaternion::new()
    }
}

impl<T: Num + Float + Copy> Quaternion<T> {
    /// Creates a new identity quaternion
    pub fn new() -> Quaternion<T> {
        Quaternion {
            x: T::zero(),
            y: T::zero(),
            z: T::zero(),
            w: T::one(),
        }
    }

    /// Creates a quaternion representing a counterclockwise rotation of `angle` radians around the
    /// given axis. This function normalizes the axis, but if the axis is `(0, 0, 0)` the quaternion
    /// will have its `x`, `y` and `z` fields set to 0.
    pub fn rotation(angle: T, axis: Vec3<T>) -> Quaternion<T> {
        let axis = axis.normalize();
        let angle = angle / (T::one() + T::one());
        let (sin, cos) = angle.sin_cos();
        Quaternion {
            x: axis.x*sin,
            y: axis.y*sin,
            z: axis.z*sin,
            w: cos,
        }
    }

    /// Calculates the length of this quaternion, raised to the power of two. Note that this is
    /// cheaper than computing the actual length.
    pub fn len_sqr(&self) -> T {
        self.x*self.x + self.y*self.y + self.z*self.z + self.w*self.w
    }

    /// Calculates the length of this quaternion.
    pub fn len(&self) -> T {
        (self.x*self.x + self.y*self.y + self.z*self.z + self.w*self.w).sqrt()
    }

    /// Normalizes this quaternion, returning a new quaternion with length 1.
    pub fn normalize(&self) -> Quaternion<T> {
        *self / self.len()
    }
}

impl<T: Num + Float + Copy> Mul for Quaternion<T> {
    type Output = Self;
    fn mul(self, other: Quaternion<T>) -> Self {
        Quaternion {
            x: self.w*other.x + self.x*other.w + self.y*other.z - self.z*other.y,
            y: self.w*other.y + self.y*other.w + self.z*other.x - self.x*other.z,
            z: self.w*other.z + self.z*other.w + self.x*other.y - self.y*other.x,
            w: self.w*other.w - self.x*other.x - self.y*other.y - self.z*other.z,
        }
    }
}
impl<T: Num + Float + Copy> MulAssign for Quaternion<T> {
    fn mul_assign(&mut self, other: Quaternion<T>) {
        let x = self.w*other.x + self.x*other.w + self.y*other.z - self.z*other.y;
        let y = self.w*other.y + self.y*other.w + self.z*other.x - self.x*other.z;
        let z = self.w*other.z + self.z*other.w + self.x*other.y - self.y*other.x;
        let w = self.w*other.w - self.x*other.x - self.y*other.y - self.z*other.z;
        self.x = x;
        self.y = y;
        self.z = z;
        self.w = w;
    }
}
impl<T: Num + Float + Copy> Mul<T> for Quaternion<T> {
    type Output = Self;
    fn mul(self, scalar: T) -> Self {
        Quaternion {
            x: self.x * scalar,
            y: self.y * scalar,
            z: self.z * scalar,
            w: self.w * scalar,
        }
    }
}
impl<T: Num + Float + Copy> MulAssign<T> for Quaternion<T> {
    fn mul_assign(&mut self, scalar: T) {
        self.x = self.x * scalar;
        self.y = self.y * scalar;
        self.z = self.z * scalar;
        self.w = self.w * scalar;
    }
}
impl<T: Num + Float + Copy> Div<T> for Quaternion<T> {
    type Output = Self;
    fn div(self, scalar: T) -> Self {
        Quaternion {
            x: self.x / scalar,
            y: self.y / scalar,
            z: self.z / scalar,
            w: self.w / scalar,
        }
    }
}
impl<T: Num + Float + Copy> DivAssign<T> for Quaternion<T> {
    fn div_assign(&mut self, scalar: T) {
        self.x = self.x / scalar;
        self.y = self.y / scalar;
        self.z = self.z / scalar;
        self.w = self.w / scalar;
    }
}

impl<T: Num + Float + Copy> From<Quaternion<T>> for Mat4<T> {
    fn from(quat: Quaternion<T>) -> Mat4<T> {
        Mat4::from_quaternion(quat.x, quat.y, quat.z, quat.w)
    }
}

#[cfg(test)]
mod tests {
    use std::f32;
    use super::*;
    use {Mat4, Vec4};

    #[test]
    fn identity() {
        let identity = Quaternion::<f32>::new();
        assert_eq!(identity, identity*identity);
        assert_eq!(identity, identity*identity*identity);
    }

    #[test]
    fn matrix_rotation() {
        let quat = Quaternion::<f32>::rotation(f32::consts::PI/2.0, Vec3::new(1.0, 0.0, 0.0));
        let mat: Mat4<f32> = quat.into();

        let a = Vec4::new(0.0, 1.0, 0.0, 1.0);
        let b = Vec4::new(0.0, 0.0, 1.0, 1.0);

        let dif = (mat*a - b).len();
        assert!(dif < 0.001);
    }

    #[test]
    fn matrix_rotation_reverse() {
        let mat_a: Mat4<f32> = Quaternion::rotation(f32::consts::PI/2.0, Vec3::new(1.0, 0.0, 0.0)).into();
        let mat_b: Mat4<f32> = Quaternion::rotation(-f32::consts::PI/2.0, Vec3::new(1.0, 0.0, 0.0)).into();

        let a = Vec4::new(0.0, 1.0, 0.0, 1.0);

        let dif = (mat_a*mat_b*a - a).len();
        assert!(dif < 0.001);
    }
}
<|endoftext|>


<commit_before>#[cfg(target_arch = "wasm32")]
compile_error!("This module should not be included when compiling to wasm");

use std::sync::mpsc::{self, TryRecvError};
use std::thread;

use piston_window::{clear, context, line, polygon, AdvancedWindow, Event as PistonEvent, G2d, Input, OpenGL, PistonWindow, WindowSettings};

use crate::app::TurtleApp;
use crate::event::from_piston_event;
use crate::extensions::ConvertScreenCoordinates;
use crate::query::DrawingCommand;
use crate::state::{DrawingState, Path, Pen, Polygon, TurtleState};
use crate::{color, Color, Event, Point};

fn update_window(window: &mut PistonWindow, current: DrawingState, next: DrawingState) -> DrawingState {
    if next.title != current.title {
        window.set_title(next.title.clone());
    }
    if next.width != current.width || next.height != current.height {
        window.set_size((next.width, next.height));
    }
    if next.maximized != current.maximized {
        window.window.window.set_maximized(next.maximized);
    }
    if next.fullscreen != current.fullscreen {
        if next.fullscreen {
            window
                .window
                .window
                .set_fullscreen(Some(window.window.window.get_current_monitor()));
        } else {
            window.window.window.set_fullscreen(None);
        }
    }
    next
}

#[derive(Debug)]
pub enum Drawing {
    Path(Path),
    Polygon(Polygon),
}

pub struct Renderer {
    app: TurtleApp,
    drawings: Vec<Drawing>,
    /// Polygon that is currently in the process of being filled
    /// Removed when EndFill is sent
    fill_polygon: Option<(Vec<Path>, Polygon)>,
}

impl Renderer {
    pub fn new(app: TurtleApp) -> Renderer {
        Self {
            app,
            drawings: Vec::new(),
            fill_polygon: None,
        }
    }

    pub fn run(&mut self, drawing_rx: mpsc::Receiver<DrawingCommand>, events_tx: mpsc::Sender<Event>) {
        let state = self.app.read_only();

        // This check isn't foolproof. Someone can always create a thread named "main".
        if thread::current().name().unwrap_or("") != "main" {
            // In order to maintain compatibility with MacOS, we need to make sure that windows are
            // only created on the main thread. We do this check on all platforms so that no one
            // can accidentally make a change that creates the window off of the main thread.
            unreachable!("bug: windows can only be created on the main thread");
        }

        let window_settings = WindowSettings::new(&*state.drawing().title, (state.drawing().width, state.drawing().height))
            .exit_on_esc(true)
            .opengl(OpenGL::V3_3)
            .srgb(false);

        // Need to create a GlutinWindow through WindowSettings::build() and then pass that to
        // PistonWindow::new(). PistonWindow has a hardcoded `srgb(true)` in its implementation
        // of BuildFromWindowSettings so we can't build() to a PistonWindow directly. We bypass
        // BuildFromWindowSettings by calling PistonWindow::new() with the properly configured
        // GlutinWindow.
        // Source: https://github.com/PistonDevelopers/piston/issues/1202#issuecomment-368338909
        // NOTE: This might lead to bugs because the shaders assume a linear color space (sRGB).
        // Source: https://github.com/PistonDevelopers/piston/issues/1202#issuecomment-338147900
        let mut window: PistonWindow = PistonWindow::new(OpenGL::V3_3, 0, window_settings.build().expect("bug: could not build window"));

        // We keep a copy of the DrawingState so that we can tell when it is updated and we need
        // to change something on the window
        let mut current_drawing = DrawingState::default();

        let mut center = state.drawing().center;

        'renderloop: while let Some(event) = window.next() {
            match event {
                PistonEvent::Input(Input::Resize(width, height)) => {
                    let width = width as u32;
                    let height = height as u32;
                    if width != current_drawing.width || height != current_drawing.height {
                        let mut drawing = self.app.drawing_mut();
                        drawing.width = width;
                        drawing.height = height;
                    }
                }
                _ => {}
            }

            if let Some(event) = from_piston_event(&event, |pt| pt.to_local_coords(center)) {
                match events_tx.send(event) {
                    Ok(_) => {}
                    // Quit - the server thread must have quit
                    Err(_) => break,
                }
            }

            // Need to handle all of the queries we receive at once so that any lag caused by
            // how long rendering takes doesn't cause any problems
            loop {
                match drawing_rx.try_recv() {
                    Ok(cmd) => self.handle_drawing_command(cmd),
                    Err(TryRecvError::Empty) => break,                    // Do nothing
                    Err(TryRecvError::Disconnected) => break 'renderloop, // Quit
                }
            }

            // Update the window based on any changes in the DrawingState
            current_drawing = update_window(&mut window, current_drawing, state.drawing().clone());

            window.draw_2d(&event, |c, g| {
                let view = c.get_view_size();
                let width = view[0] as f64;
                let height = view[1] as f64;
                center = state.drawing().center.to_screen_coords(Point {
                    x: width * 0.5,
                    y: height * 0.5,
                });

                // We clone the relevant state before rendering so that the rendering thread
                // doesn't need to keep locking, waiting or making the main thread wait
                let drawing = state.drawing().clone();
                let temporary_path = state.temporary_path().clone();
                let turtle = state.turtle().clone();

                self.render(c, g, center, &drawing, &temporary_path, &turtle);
            });
        }
    }

    /// Handles a drawing command sent from the main thread
    fn handle_drawing_command(&mut self, command: DrawingCommand) {
        //NOTE: Do not pass the ReadOnly state to this function. By the time a DrawingCommand is
        // handled, that state may be completely out of date
        use self::DrawingCommand::*;
        match command {
            StorePath(path) => {
                if self.fill_polygon.is_some() {
                    let &mut (ref mut border, ref mut poly) = self.fill_polygon.as_mut().unwrap();
                    border.push(path.clone());

                    let Path { start, end, .. } = path;
                    if poly.vertices.last().map_or(true, |&v| v != start) {
                        poly.vertices.push(start);
                    }
                    poly.vertices.push(end);
                } else if path.pen.enabled {
                    self.drawings.push(Drawing::Path(path));
                }
            }
            BeginFill(fill_color) => {
                // Calling begin_fill multiple times is okay, it just won't do anything until
                // end_fill is called
                self.fill_polygon = self.fill_polygon.take().or_else(|| {
                    Some((
                        Vec::new(),
                        Polygon {
                            vertices: Vec::new(),
                            fill_color,
                        },
                    ))
                });
            }
            // Calling end_fill multiple times is not a problem
            EndFill => if let Some((border, poly)) = self.fill_polygon.take() {
                // Always add the border over the filled polygon so the border is drawn on top
                self.drawings.push(Drawing::Polygon(poly));
                self.drawings.extend(
                    border
                        .into_iter()
                        .filter_map(|p| if p.pen.enabled { Some(Drawing::Path(p)) } else { None }),
                );
            },
            Clear => {
                self.drawings.clear();
                // Because clear doesn't actually shorten the capacity of the vector
                // We don't want to leak memory by leaving this Vec at whatever its size was before
                self.drawings.shrink_to_fit();
                self.fill_polygon.take();
            }
        }
    }

    /// The main rendering route. Dispatches to other functions as needed.
    fn render(
        &self,
        c: context::Context,
        g: &mut G2d<'_>,
        center: Point,
        drawing: &DrawingState,
        temporary_path: &Option<Path>,
        turtle: &TurtleState,
    ) {
        let background = drawing.background;
        clear(background.into(), g);

        for drawing in &self.drawings {
            match *drawing {
                Drawing::Path(ref path) => self.render_path(c, g, center, path),
                Drawing::Polygon(ref poly) => self.render_polygon(c, g, center, poly.fill_color, poly.vertices.iter()),
            }
        }

        if let Some(&(ref border, ref poly)) = self.fill_polygon.as_ref() {
            // If the temporary_path is not None, we need to add it to the polygon being
            // filled or else the polygon will fall one edge behind in the animation
            let extra = temporary_path.as_ref().map_or(Vec::new(), |&Path { start, end, .. }| {
                if poly.vertices.last().map_or(true, |&v| v != start) {
                    vec![start, end]
                } else {
                    vec![end]
                }
            });
            self.render_polygon(c, g, center, poly.fill_color, poly.vertices.iter().chain(extra.iter()));

            for path in border {
                if path.pen.enabled {
                    self.render_path(c, g, center, path);
                }
            }
        }

        if let Some(ref path) = *temporary_path {
            if path.pen.enabled {
                self.render_path(c, g, center, path);
            }
        }

        self.render_shell(c, g, center, turtle);
    }

    /// Render a path assuming that its pen is enabled
    fn render_path(&self, c: context::Context, g: &mut G2d<'_>, center: Point, path: &Path) {
        let &Path { start, end, ref pen } = path;
        let &Pen { thickness, color, enabled } = pen;
        debug_assert!(enabled, "bug: attempt to render path when pen was not enabled");

        let start = start.to_screen_coords(center);
        let end = end.to_screen_coords(center);

        line(color.into(), thickness, [start.x, start.y, end.x, end.y], c.transform, g);
    }

    /// Render a polygon given its vertices
    fn render_polygon<'a, T: Iterator<Item = &'a Point>>(
        &self,
        c: context::Context,
        g: &mut G2d<'_>,
        center: Point,
        fill_color: Color,
        verts: T,
    ) {
        // Performance note: Why make this function generic instead of just taking Polygon?
        // Answer: render_polygon is called multiple times. It's called repeatedly in a loop, and
        // it is also called when drawing the current fill_polygon. Rather than moving the
        // code to *maybe* add the temporary_path to the rendered polygon into this method, we
        // avoid branching unnecessarily by allowing the repeated caller to do what is fast, then
        // doing the slow thing (checking if the path is None) only once when it is needed.
        //
        // We pass in the points as an Iterator so that they do not need to be collected into any
        // struct. This avoids an allocation that really isn't needed since these are all temporary
        // anyway. Everything is going to get copied anyway on the next line. No need to do it
        // twice.
        //
        // See the commit before this comment was added for the approach that would have required
        // branching in every iteration of the loop where render_polygon is called over and over
        // again.
        let verts = verts.map(|p| p.to_screen_coords(center).into()).collect::<Vec<_>>();
        polygon(fill_color.into(), &verts, c.transform, g);
    }

    /// Draw the turtle's shell
    fn render_shell(
        &self,
        c: context::Context,
        g: &mut G2d<'_>,
        center: Point,
        &TurtleState {
            position,
            heading,
            visible,
            ..
        }: &TurtleState,
    ) {
        // Calculate all the points on the shell by rotating the shell shape by the turtle's
        // heading and moving it to the turtle's position
        if !visible {
            return;
        }

        let cos = heading.cos();
        let sin = heading.sin();
        let turtle_x = position.x;
        let turtle_y = position.y;
        let shell: Vec<_> = [[0., 15.], [10., 0.], [0., -15.]]
            .into_iter()
            .map(|pt| {
                // Rotate each point by the heading and add the current turtle position
                let x = cos * pt[0] - sin * pt[1] + turtle_x;
                let y = sin * pt[0] + cos * pt[1] + turtle_y;
                Point { x, y }.to_screen_coords(center).into()
            })
            .collect();

        // Draw the turtle shell with its background first, then its border
        polygon(color::WHITE.into(), &shell, c.transform, g);
        for i in 0..shell.len() {
            let start = shell[i];
            let end = shell[(i + 1) % shell.len()];

            line(color::BLACK.into(), 1., [start[0], start[1], end[0], end[1]], c.transform, g);
        }
    }
}
<commit_msg>Experimenting with going back to the old way of building a window<commit_after>#[cfg(target_arch = "wasm32")]
compile_error!("This module should not be included when compiling to wasm");

use std::sync::mpsc::{self, TryRecvError};
use std::thread;

use piston_window::{clear, context, line, polygon, AdvancedWindow, Event as PistonEvent, G2d,
    Input, PistonWindow, WindowSettings};

use crate::app::TurtleApp;
use crate::event::from_piston_event;
use crate::extensions::ConvertScreenCoordinates;
use crate::query::DrawingCommand;
use crate::state::{DrawingState, Path, Pen, Polygon, TurtleState};
use crate::{color, Color, Event, Point};

fn update_window(window: &mut PistonWindow, current: DrawingState, next: DrawingState) -> DrawingState {
    if next.title != current.title {
        window.set_title(next.title.clone());
    }
    if next.width != current.width || next.height != current.height {
        window.set_size((next.width, next.height));
    }
    if next.maximized != current.maximized {
        window.window.window.set_maximized(next.maximized);
    }
    if next.fullscreen != current.fullscreen {
        if next.fullscreen {
            window
                .window
                .window
                .set_fullscreen(Some(window.window.window.get_current_monitor()));
        } else {
            window.window.window.set_fullscreen(None);
        }
    }
    next
}

#[derive(Debug)]
pub enum Drawing {
    Path(Path),
    Polygon(Polygon),
}

pub struct Renderer {
    app: TurtleApp,
    drawings: Vec<Drawing>,
    /// Polygon that is currently in the process of being filled
    /// Removed when EndFill is sent
    fill_polygon: Option<(Vec<Path>, Polygon)>,
}

impl Renderer {
    pub fn new(app: TurtleApp) -> Renderer {
        Self {
            app,
            drawings: Vec::new(),
            fill_polygon: None,
        }
    }

    pub fn run(&mut self, drawing_rx: mpsc::Receiver<DrawingCommand>, events_tx: mpsc::Sender<Event>) {
        let state = self.app.read_only();

        // This check isn't foolproof. Someone can always create a thread named "main".
        if thread::current().name().unwrap_or("") != "main" {
            // In order to maintain compatibility with MacOS, we need to make sure that windows are
            // only created on the main thread. We do this check on all platforms so that no one
            // can accidentally make a change that creates the window off of the main thread.
            unreachable!("bug: windows can only be created on the main thread");
        }

        let mut window: PistonWindow = WindowSettings::new(
            &*state.drawing().title,
            (state.drawing().width, state.drawing().height)
        ).exit_on_esc(true).build().expect("bug: could not build window");

        // We keep a copy of the DrawingState so that we can tell when it is updated and we need
        // to change something on the window
        let mut current_drawing = DrawingState::default();

        let mut center = state.drawing().center;

        'renderloop: while let Some(event) = window.next() {
            match event {
                PistonEvent::Input(Input::Resize(width, height)) => {
                    let width = width as u32;
                    let height = height as u32;
                    if width != current_drawing.width || height != current_drawing.height {
                        let mut drawing = self.app.drawing_mut();
                        drawing.width = width;
                        drawing.height = height;
                    }
                }
                _ => {}
            }

            if let Some(event) = from_piston_event(&event, |pt| pt.to_local_coords(center)) {
                match events_tx.send(event) {
                    Ok(_) => {}
                    // Quit - the server thread must have quit
                    Err(_) => break,
                }
            }

            // Need to handle all of the queries we receive at once so that any lag caused by
            // how long rendering takes doesn't cause any problems
            loop {
                match drawing_rx.try_recv() {
                    Ok(cmd) => self.handle_drawing_command(cmd),
                    Err(TryRecvError::Empty) => break,                    // Do nothing
                    Err(TryRecvError::Disconnected) => break 'renderloop, // Quit
                }
            }

            // Update the window based on any changes in the DrawingState
            current_drawing = update_window(&mut window, current_drawing, state.drawing().clone());

            window.draw_2d(&event, |c, g| {
                let view = c.get_view_size();
                let width = view[0] as f64;
                let height = view[1] as f64;
                center = state.drawing().center.to_screen_coords(Point {
                    x: width * 0.5,
                    y: height * 0.5,
                });

                // We clone the relevant state before rendering so that the rendering thread
                // doesn't need to keep locking, waiting or making the main thread wait
                let drawing = state.drawing().clone();
                let temporary_path = state.temporary_path().clone();
                let turtle = state.turtle().clone();

                self.render(c, g, center, &drawing, &temporary_path, &turtle);
            });
        }
    }

    /// Handles a drawing command sent from the main thread
    fn handle_drawing_command(&mut self, command: DrawingCommand) {
        //NOTE: Do not pass the ReadOnly state to this function. By the time a DrawingCommand is
        // handled, that state may be completely out of date
        use self::DrawingCommand::*;
        match command {
            StorePath(path) => {
                if self.fill_polygon.is_some() {
                    let &mut (ref mut border, ref mut poly) = self.fill_polygon.as_mut().unwrap();
                    border.push(path.clone());

                    let Path { start, end, .. } = path;
                    if poly.vertices.last().map_or(true, |&v| v != start) {
                        poly.vertices.push(start);
                    }
                    poly.vertices.push(end);
                } else if path.pen.enabled {
                    self.drawings.push(Drawing::Path(path));
                }
            }
            BeginFill(fill_color) => {
                // Calling begin_fill multiple times is okay, it just won't do anything until
                // end_fill is called
                self.fill_polygon = self.fill_polygon.take().or_else(|| {
                    Some((
                        Vec::new(),
                        Polygon {
                            vertices: Vec::new(),
                            fill_color,
                        },
                    ))
                });
            }
            // Calling end_fill multiple times is not a problem
            EndFill => if let Some((border, poly)) = self.fill_polygon.take() {
                // Always add the border over the filled polygon so the border is drawn on top
                self.drawings.push(Drawing::Polygon(poly));
                self.drawings.extend(
                    border
                        .into_iter()
                        .filter_map(|p| if p.pen.enabled { Some(Drawing::Path(p)) } else { None }),
                );
            },
            Clear => {
                self.drawings.clear();
                // Because clear doesn't actually shorten the capacity of the vector
                // We don't want to leak memory by leaving this Vec at whatever its size was before
                self.drawings.shrink_to_fit();
                self.fill_polygon.take();
            }
        }
    }

    /// The main rendering route. Dispatches to other functions as needed.
    fn render(
        &self,
        c: context::Context,
        g: &mut G2d<'_>,
        center: Point,
        drawing: &DrawingState,
        temporary_path: &Option<Path>,
        turtle: &TurtleState,
    ) {
        let background = drawing.background;
        clear(background.into(), g);

        for drawing in &self.drawings {
            match *drawing {
                Drawing::Path(ref path) => self.render_path(c, g, center, path),
                Drawing::Polygon(ref poly) => self.render_polygon(c, g, center, poly.fill_color, poly.vertices.iter()),
            }
        }

        if let Some(&(ref border, ref poly)) = self.fill_polygon.as_ref() {
            // If the temporary_path is not None, we need to add it to the polygon being
            // filled or else the polygon will fall one edge behind in the animation
            let extra = temporary_path.as_ref().map_or(Vec::new(), |&Path { start, end, .. }| {
                if poly.vertices.last().map_or(true, |&v| v != start) {
                    vec![start, end]
                } else {
                    vec![end]
                }
            });
            self.render_polygon(c, g, center, poly.fill_color, poly.vertices.iter().chain(extra.iter()));

            for path in border {
                if path.pen.enabled {
                    self.render_path(c, g, center, path);
                }
            }
        }

        if let Some(ref path) = *temporary_path {
            if path.pen.enabled {
                self.render_path(c, g, center, path);
            }
        }

        self.render_shell(c, g, center, turtle);
    }

    /// Render a path assuming that its pen is enabled
    fn render_path(&self, c: context::Context, g: &mut G2d<'_>, center: Point, path: &Path) {
        let &Path { start, end, ref pen } = path;
        let &Pen { thickness, color, enabled } = pen;
        debug_assert!(enabled, "bug: attempt to render path when pen was not enabled");

        let start = start.to_screen_coords(center);
        let end = end.to_screen_coords(center);

        line(color.into(), thickness, [start.x, start.y, end.x, end.y], c.transform, g);
    }

    /// Render a polygon given its vertices
    fn render_polygon<'a, T: Iterator<Item = &'a Point>>(
        &self,
        c: context::Context,
        g: &mut G2d<'_>,
        center: Point,
        fill_color: Color,
        verts: T,
    ) {
        // Performance note: Why make this function generic instead of just taking Polygon?
        // Answer: render_polygon is called multiple times. It's called repeatedly in a loop, and
        // it is also called when drawing the current fill_polygon. Rather than moving the
        // code to *maybe* add the temporary_path to the rendered polygon into this method, we
        // avoid branching unnecessarily by allowing the repeated caller to do what is fast, then
        // doing the slow thing (checking if the path is None) only once when it is needed.
        //
        // We pass in the points as an Iterator so that they do not need to be collected into any
        // struct. This avoids an allocation that really isn't needed since these are all temporary
        // anyway. Everything is going to get copied anyway on the next line. No need to do it
        // twice.
        //
        // See the commit before this comment was added for the approach that would have required
        // branching in every iteration of the loop where render_polygon is called over and over
        // again.
        let verts = verts.map(|p| p.to_screen_coords(center).into()).collect::<Vec<_>>();
        polygon(fill_color.into(), &verts, c.transform, g);
    }

    /// Draw the turtle's shell
    fn render_shell(
        &self,
        c: context::Context,
        g: &mut G2d<'_>,
        center: Point,
        &TurtleState {
            position,
            heading,
            visible,
            ..
        }: &TurtleState,
    ) {
        // Calculate all the points on the shell by rotating the shell shape by the turtle's
        // heading and moving it to the turtle's position
        if !visible {
            return;
        }

        let cos = heading.cos();
        let sin = heading.sin();
        let turtle_x = position.x;
        let turtle_y = position.y;
        let shell: Vec<_> = [[0., 15.], [10., 0.], [0., -15.]]
            .into_iter()
            .map(|pt| {
                // Rotate each point by the heading and add the current turtle position
                let x = cos * pt[0] - sin * pt[1] + turtle_x;
                let y = sin * pt[0] + cos * pt[1] + turtle_y;
                Point { x, y }.to_screen_coords(center).into()
            })
            .collect();

        // Draw the turtle shell with its background first, then its border
        polygon(color::WHITE.into(), &shell, c.transform, g);
        for i in 0..shell.len() {
            let start = shell[i];
            let end = shell[(i + 1) % shell.len()];

            line(color::BLACK.into(), 1., [start[0], start[1], end[0], end[1]], c.transform, g);
        }
    }
}
<|endoftext|>


<commit_before>#[cfg(feature = "hot-resource")]
mod hot {
  use notify::{self, RecommendedWatcher, Watcher};
  use std::collections::BTreeMap;
  use std::path::{Path, PathBuf};
  use std::sync::{Arc, Mutex};
  use std::sync::mpsc;
  use std::thread;

  /// Time to await after a resource update to establish that it should be reloaded.
  pub const UPDATE_AWAIT_TIME: f64 = 0.1; // 100ms

  pub struct ResourceManager {
    receivers: Arc<Mutex<BTreeMap<PathBuf, mpsc::Sender<()>>>>
  }

  impl ResourceManager {
    pub fn new<P>(root: P) -> Self where P: AsRef<Path> {
      let (wsx, wrx) = mpsc::channel();
      let mut watcher: RecommendedWatcher = Watcher::new(wsx).unwrap();
      let receivers: Arc<Mutex<BTreeMap<PathBuf, mpsc::Sender<()>>>> = Arc::new(Mutex::new(BTreeMap::new()));
      let receivers_ = receivers.clone();
      let root = root.as_ref().to_path_buf();

      let _ = thread::spawn(move || {
        let _ = watcher.watch(root);

        for event in wrx.iter() {
          match event {
            notify::Event { path: Some(path), op: Ok(notify::op::WRITE) } => {
              if let Some(sx) = receivers_.lock().unwrap().get(&path) {
                sx.send(()).unwrap();
              }
            },
            _ => {}
          }
        }
      });

      ResourceManager {
        receivers: receivers
      }
    }

    pub fn monitor<P>(&mut self, path: P, sx: mpsc::Sender<()>) where P: AsRef<Path> {
      let mut receivers = self.receivers.lock().unwrap();

      receivers.insert(path.as_ref().to_path_buf(), sx);
    }
  }
}

#[cfg(not(feature = "hot-resource"))]
mod cold {
  use std::path::Path;
  use std::sync::mpsc;

  pub struct ResourceManager {}

  impl ResourceManager {
    pub fn new<P>(_: P) -> Self where P: AsRef<Path> {
      ResourceManager {}
    }

    pub fn monitor<P>(&mut self, _: P, _: mpsc::Sender<()>) where P: AsRef<Path> {}
  }
}

#[cfg(feature = "hot-resource")]
pub use self::hot::*;
#[cfg(not(feature = "hot-resource"))]
pub use self::cold::*;

/// Sync all the resources passed in as arguments.
#[macro_export]
macro_rules! sync {
  ($( $r:ident ),*) => {
    $( $r.sync(); )*
  }
}

/// A helper macro to declare a `sync` public method for a resource. The resource must
/// have a `last_update_time: Option<f64>` and a `reload(&mut self)` function.
#[cfg(feature = "hot-resource")]
#[macro_export]
macro_rules! decl_sync_hot {
  () => {
    pub fn sync(&mut self) {
      if self.rx.try_recv().is_ok() {
        self.last_update_time = Some(::time::precise_time_s());
      }

      match self.last_update_time {
        Some(last_update_time) if ::time::precise_time_s() - last_update_time >= ::resource::UPDATE_AWAIT_TIME => {
          self.reload();
          self.last_update_time = None;
        },
        _ => {}
      }
    }
  }
}
<commit_msg>Fixed sync!<commit_after>#[cfg(feature = "hot-resource")]
mod hot {
  use notify::{self, RecommendedWatcher, Watcher};
  use std::collections::BTreeMap;
  use std::path::{Path, PathBuf};
  use std::sync::{Arc, Mutex};
  use std::sync::mpsc;
  use std::thread;

  /// Time to await after a resource update to establish that it should be reloaded.
  pub const UPDATE_AWAIT_TIME: f64 = 0.1; // 100ms

  pub struct ResourceManager {
    receivers: Arc<Mutex<BTreeMap<PathBuf, mpsc::Sender<()>>>>
  }

  impl ResourceManager {
    pub fn new<P>(root: P) -> Self where P: AsRef<Path> {
      let (wsx, wrx) = mpsc::channel();
      let mut watcher: RecommendedWatcher = Watcher::new(wsx).unwrap();
      let receivers: Arc<Mutex<BTreeMap<PathBuf, mpsc::Sender<()>>>> = Arc::new(Mutex::new(BTreeMap::new()));
      let receivers_ = receivers.clone();
      let root = root.as_ref().to_path_buf();

      let _ = thread::spawn(move || {
        let _ = watcher.watch(root);

        for event in wrx.iter() {
          match event {
            notify::Event { path: Some(path), op: Ok(notify::op::WRITE) } => {
              if let Some(sx) = receivers_.lock().unwrap().get(&path) {
                sx.send(()).unwrap();
              }
            },
            _ => {}
          }
        }
      });

      ResourceManager {
        receivers: receivers
      }
    }

    pub fn monitor<P>(&mut self, path: P, sx: mpsc::Sender<()>) where P: AsRef<Path> {
      let mut receivers = self.receivers.lock().unwrap();

      receivers.insert(path.as_ref().to_path_buf(), sx);
    }
  }
}

#[cfg(not(feature = "hot-resource"))]
mod cold {
  use std::path::Path;
  use std::sync::mpsc;

  pub struct ResourceManager {}

  impl ResourceManager {
    pub fn new<P>(_: P) -> Self where P: AsRef<Path> {
      ResourceManager {}
    }

    pub fn monitor<P>(&mut self, _: P, _: mpsc::Sender<()>) where P: AsRef<Path> {}
  }
}

#[cfg(feature = "hot-resource")]
pub use self::hot::*;
#[cfg(not(feature = "hot-resource"))]
pub use self::cold::*;

/// Sync all the resources passed in as arguments.
#[macro_export]
macro_rules! sync {
  ($( $r:expr ),*) => {
    $( $r.sync(); )*
  }
}

/// A helper macro to declare a `sync` public method for a resource. The resource must
/// have a `last_update_time: Option<f64>` and a `reload(&mut self)` function.
#[cfg(feature = "hot-resource")]
#[macro_export]
macro_rules! decl_sync_hot {
  () => {
    pub fn sync(&mut self) {
      if self.rx.try_recv().is_ok() {
        self.last_update_time = Some(::time::precise_time_s());
      }

      match self.last_update_time {
        Some(last_update_time) if ::time::precise_time_s() - last_update_time >= ::resource::UPDATE_AWAIT_TIME => {
          self.reload();
          self.last_update_time = None;
        },
        _ => {}
      }
    }
  }
}
<|endoftext|>


<commit_before>use common::{Header, HTTPVersion, StatusCode};
use std::io::{IoResult, MemReader};
use std::io::fs::File;
use std::io::util;
use std::io::util::NullReader;
use chunks::ChunksEncoder;

/// Object representing an HTTP response whose purpose is to be given to a `Request`.
///
/// Some headers cannot be changed. Trying to define the value
///  of one of these will have no effect:
///
///  - `Accept-Ranges`
///  - `Connection`
///  - `Content-Range`
///  - `Trailer`
///  - `Transfer-Encoding`
///  - `Upgrade`
///
/// Some headers have special behaviors:
///
///  - `Content-Encoding`: If you define this header, the library
///      will assume that the data from the `Reader` has the specified encoding
///      and will just pass-through.
///  - `Content-Length`: If you define this header to `N`, only the first `N` bytes
///      of the `Reader` will be read. If the `Reader` reaches `EOF` before `N` bytes have
///      been read, `0`s will be sent. Also, this header may not be passed to the final
///      output.
///
#[experimental]
pub struct Response<R> {
    reader: R,
    status_code: StatusCode,
    headers: Vec<Header>,
    data_length: Option<uint>,
}

/// Builds a Date: header with the current date.
// TODO:
fn build_date_header() -> Header {
    from_str("Date: unknown").unwrap()
}

impl<R: Reader> Response<R> {
    #[experimental]
    pub fn new(status_code: StatusCode, headers: Vec<Header>,
               data: R, data_length: Option<uint>) -> Response<R>
    {
        let mut response = Response {
            reader: data,
            status_code: status_code,
            headers: Vec::new(),
            data_length: data_length,
        };

        for h in headers.move_iter() {
            response.add_header(h)
        }

        response
    }

    /// Adds a header to the list.
    /// Does all the checks.
    #[experimental]
    pub fn add_header(&mut self, header: Header) {
        // ignoring forbidden headers
        if header.field.equiv(&"Accept-Ranges") ||
           header.field.equiv(&"Connection") ||
           header.field.equiv(&"Content-Range") ||
           header.field.equiv(&"Trailer") ||
           header.field.equiv(&"Transfer-Encoding") ||
           header.field.equiv(&"Upgrade")
        {
            return;
        }

        // if the header is Content-Length, setting the data length
        if header.field.equiv(&"Content-Length") {
            match from_str::<uint>(header.value.as_slice()) {
                Some(val) => self.data_length = Some(val),
                None => ()      // wrong value for content-length
            };

            return;
        }

        self.headers.push(header)
    }

    /// Returns the same request, but with an additional header.
    ///
    /// Some headers cannot be modified and some other have a
    ///  special behavior. See the documentation above.
    #[unstable]
    pub fn with_header(mut self, header: Header) -> Response<R> {
        self.add_header(header);
        self
    }

    /// Returns the same request, but with a different status code.
    #[unstable]
    pub fn with_status_code(mut self, code: StatusCode) -> Response<R> {
        self.status_code = code;
        self
    }

    /// Prints the HTTP response to a writer.
    ///
    /// This function is the one used to send the response to the client's socket.
    /// Therefore you shouldn't expect anything pretty-printed or even readable.
    ///
    /// The HTTP version and headers passed as arguments are used to
    ///  decide which features (most notably, encoding) to use.
    // TODO: status codes 1xx, 204 and 304 must not include a response body
    #[unstable]
    pub fn raw_print<W: Writer>(mut self, mut writer: W, http_version: HTTPVersion,
                                request_headers: &[Header]) -> IoResult<()>
    {
        // if we don't have a Content-Length, or if the Content-Length is too big, using chunks writer
        let chunks_threshold = 32768;
        let use_chunks =
            http_version >= HTTPVersion(1, 1) &&
            self.data_length.as_ref().filtered(|val| **val < chunks_threshold).is_none();

        // add `Date` if not in the headers
        if self.headers.iter().find(|h| h.field.equiv(&"Date")).is_none() {
            self.headers.unshift(build_date_header());
        }

        // add `Server` if not in the headers
        if self.headers.iter().find(|h| h.field.equiv(&"Server")).is_none() {
            self.headers.unshift(
                from_str("Server: tiny-http (Rust)").unwrap()
            );
        }

        // add transfer-encoding header
        if use_chunks {
            self.headers.push(
                Header{field: from_str("Transfer-Encoding").unwrap(), value: "chunked".to_string()}
            )
        }

        // writing status line
        try!(write!(writer, "HTTP/{} {} {}\r\n",
            http_version,
            self.status_code.as_uint(),
            self.status_code.get_default_reason_phrase()
        ));

        // writing headers
        for header in self.headers.iter() {
            try!(write!(writer, "{}: {}\r\n", header.field, header.value));
        }

        // separator between header and data
        try!(write!(writer, "\r\n"));

        // writing data
        if use_chunks {
            let mut writer = ChunksEncoder::new(writer);
            try!(util::copy(&mut self.reader, &mut writer));
        } else {
            use util::EqualReader;
            assert!(self.data_length.is_some());
            let (mut equ_reader, _) = EqualReader::new(self.reader.by_ref(), self.data_length.unwrap());
            try!(util::copy(&mut equ_reader, &mut writer));
        }

        Ok(())
    }
}

impl Response<File> {
    /// Builds a new `Response` from a `File`.
    ///
    /// The `Content-Type` will **not** be automatically detected,
    ///  you must set it yourself.
    #[experimental]
    pub fn from_file(mut file: File) -> Response<File> {
        let file_size = file.stat().ok().map(|v| v.size as uint);

        Response::new(
            StatusCode(200),
            Vec::new(),
            file,
            file_size
        )
    }
}

impl Response<MemReader> {
    #[experimental]
    pub fn from_data(data: Vec<u8>) -> Response<MemReader> {
        let data_len = data.len();

        Response::new(
            StatusCode(200),
            Vec::new(),
            MemReader::new(data),
            Some(data_len)
        )
    }

    #[experimental]
    pub fn from_string(data: String) -> Response<MemReader> {
        let data_len = data.len();

        Response::new(
            StatusCode(200),
            vec!(
                from_str("Content-Type: text/plain; charset=UTF-8").unwrap()
            ),
            MemReader::new(data.into_bytes()),
            Some(data_len)
        )
    }
}

impl Response<NullReader> {
    /// Builds an empty `Response` with the given status code.
    #[experimental]
    pub fn new_empty(status_code: StatusCode) -> Response<NullReader> {
        Response::new(
            status_code,
            Vec::new(),
            NullReader,
            Some(0)
        )
    }
}
<commit_msg>Cleaner implementation of Response<commit_after>use common::{Header, HTTPVersion, StatusCode};
use std::io::{IoResult, MemReader};
use std::io::fs::File;
use std::io::util;
use std::io::util::NullReader;
use chunks::ChunksEncoder;

/// Object representing an HTTP response whose purpose is to be given to a `Request`.
///
/// Some headers cannot be changed. Trying to define the value
///  of one of these will have no effect:
///
///  - `Accept-Ranges`
///  - `Connection`
///  - `Content-Range`
///  - `Trailer`
///  - `Transfer-Encoding`
///  - `Upgrade`
///
/// Some headers have special behaviors:
///
///  - `Content-Encoding`: If you define this header, the library
///      will assume that the data from the `Reader` has the specified encoding
///      and will just pass-through.
///  - `Content-Length`: If you define this header to `N`, only the first `N` bytes
///      of the `Reader` will be read. If the `Reader` reaches `EOF` before `N` bytes have
///      been read, `0`s will be sent. Also, this header may not be passed to the final
///      output.
///
#[experimental]
pub struct Response<R> {
    reader: R,
    status_code: StatusCode,
    headers: Vec<Header>,
    data_length: Option<uint>,
}

/// Transfer encoding to use when sending the message.
/// Note that only *supported* encoding are listed here.
enum TransferEncoding {
    Identity,
    Chunked,
}

/// Builds a Date: header with the current date.
// TODO:
fn build_date_header() -> Header {
    from_str("Date: unknown").unwrap()
}

fn write_message_header<W: Writer>(mut writer: W, http_version: &HTTPVersion,
                                   status_code: &StatusCode, headers: &[Header])
    -> IoResult<()>
{
    // writing status line
    try!(write!(writer, "HTTP/{} {} {}\r\n",
        http_version,
        status_code.as_uint(),
        status_code.get_default_reason_phrase()
    ));

    // writing headers
    for header in headers.iter() {
        try!(write!(writer, "{}: {}\r\n", header.field, header.value));
    }

    // separator between header and data
    try!(write!(writer, "\r\n"));

    Ok(())
}

fn choose_transfer_encoding(request_headers: &[Header], http_version: &HTTPVersion,
                            entity_length: &Option<uint>)
    -> TransferEncoding
{
    // if we don't have a Content-Length, or if the Content-Length is too big, using chunks writer
    let chunks_threshold = 32768;
    if
        *http_version >= HTTPVersion(1, 1) &&
        entity_length.as_ref().filtered(|val| **val < chunks_threshold).is_none()
    {
        Chunked
    } else {
        Identity
    }
}

impl<R: Reader> Response<R> {
    #[experimental]
    pub fn new(status_code: StatusCode, headers: Vec<Header>,
               data: R, data_length: Option<uint>) -> Response<R>
    {
        let mut response = Response {
            reader: data,
            status_code: status_code,
            headers: Vec::new(),
            data_length: data_length,
        };

        for h in headers.move_iter() {
            response.add_header(h)
        }

        response
    }

    /// Adds a header to the list.
    /// Does all the checks.
    #[experimental]
    pub fn add_header(&mut self, header: Header) {
        // ignoring forbidden headers
        if header.field.equiv(&"Accept-Ranges") ||
           header.field.equiv(&"Connection") ||
           header.field.equiv(&"Content-Range") ||
           header.field.equiv(&"Trailer") ||
           header.field.equiv(&"Transfer-Encoding") ||
           header.field.equiv(&"Upgrade")
        {
            return;
        }

        // if the header is Content-Length, setting the data length
        if header.field.equiv(&"Content-Length") {
            match from_str::<uint>(header.value.as_slice()) {
                Some(val) => self.data_length = Some(val),
                None => ()      // wrong value for content-length
            };

            return;
        }

        self.headers.push(header)
    }

    /// Returns the same request, but with an additional header.
    ///
    /// Some headers cannot be modified and some other have a
    ///  special behavior. See the documentation above.
    #[unstable]
    pub fn with_header(mut self, header: Header) -> Response<R> {
        self.add_header(header);
        self
    }

    /// Returns the same request, but with a different status code.
    #[unstable]
    pub fn with_status_code(mut self, code: StatusCode) -> Response<R> {
        self.status_code = code;
        self
    }

    /// Prints the HTTP response to a writer.
    ///
    /// This function is the one used to send the response to the client's socket.
    /// Therefore you shouldn't expect anything pretty-printed or even readable.
    ///
    /// The HTTP version and headers passed as arguments are used to
    ///  decide which features (most notably, encoding) to use.
    // TODO: status codes 1xx, 204 and 304 must not include a response body
    #[unstable]
    pub fn raw_print<W: Writer>(mut self, mut writer: W, http_version: HTTPVersion,
                                request_headers: &[Header]) -> IoResult<()>
    {
        let transfer_encoding = choose_transfer_encoding(request_headers,
                                    &http_version, &self.data_length);

        // add `Date` if not in the headers
        if self.headers.iter().find(|h| h.field.equiv(&"Date")).is_none() {
            self.headers.unshift(build_date_header());
        }

        // add `Server` if not in the headers
        if self.headers.iter().find(|h| h.field.equiv(&"Server")).is_none() {
            self.headers.unshift(
                from_str("Server: tiny-http (Rust)").unwrap()
            );
        }

        // writing data
        match transfer_encoding {
            Chunked => {
                self.headers.push(
                    from_str("Transfer-Encoding: chunked").unwrap()
                );

                try!(write_message_header(writer.by_ref(), &http_version, &self.status_code, self.headers.as_slice()));

                let mut writer = ChunksEncoder::new(writer);
                try!(util::copy(&mut self.reader, &mut writer));
            },

            Identity => {
                use util::EqualReader;

                assert!(self.data_length.is_some());

                self.headers.push(
                    from_str(format!("Content-Length: {}", self.data_length.unwrap()).as_slice()).unwrap()
                );

                try!(write_message_header(writer.by_ref(), &http_version, &self.status_code, self.headers.as_slice()));

                assert!(self.data_length.is_some());
                let (mut equ_reader, _) = EqualReader::new(self.reader.by_ref(), self.data_length.unwrap());
                try!(util::copy(&mut equ_reader, &mut writer));
            }
        };

        Ok(())
    }
}

impl Response<File> {
    /// Builds a new `Response` from a `File`.
    ///
    /// The `Content-Type` will **not** be automatically detected,
    ///  you must set it yourself.
    #[experimental]
    pub fn from_file(mut file: File) -> Response<File> {
        let file_size = file.stat().ok().map(|v| v.size as uint);

        Response::new(
            StatusCode(200),
            Vec::new(),
            file,
            file_size
        )
    }
}

impl Response<MemReader> {
    #[experimental]
    pub fn from_data(data: Vec<u8>) -> Response<MemReader> {
        let data_len = data.len();

        Response::new(
            StatusCode(200),
            Vec::new(),
            MemReader::new(data),
            Some(data_len)
        )
    }

    #[experimental]
    pub fn from_string(data: String) -> Response<MemReader> {
        let data_len = data.len();

        Response::new(
            StatusCode(200),
            vec!(
                from_str("Content-Type: text/plain; charset=UTF-8").unwrap()
            ),
            MemReader::new(data.into_bytes()),
            Some(data_len)
        )
    }
}

impl Response<NullReader> {
    /// Builds an empty `Response` with the given status code.
    #[experimental]
    pub fn new_empty(status_code: StatusCode) -> Response<NullReader> {
        Response::new(
            status_code,
            Vec::new(),
            NullReader,
            Some(0)
        )
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{utils::project_root, Result};
use determinator::rules::DeterminatorRules;
use guppy::graph::summaries::CargoOptionsSummary;
use serde::{Deserialize, Serialize};
use std::{
    collections::{BTreeMap, HashMap, HashSet},
    fs,
    path::{Path, PathBuf},
};

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    /// Package exceptions which need to be run special
    system_tests: HashMap<String, Package>,
    /// Configuration for generating summaries
    summaries: SummariesConfig,
    /// Workspace configuration
    workspace: WorkspaceConfig,
    /// Clippy configureation
    clippy: Clippy,
    /// Fix configureation
    fix: Fix,
    /// Cargo configuration
    cargo: CargoConfig,
    grcov: CargoTool,
    /// Determinator configuration
    determinator: DeterminatorRules,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoTool {
    pub installer: CargoInstallation,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Sccache {
    /// Sccache Url
    pub installer: CargoInstallation,
    /// Where cargo home must reside for sccache to function properly.  Paths are embedded in binaries by rustc.
    pub required_cargo_home: String,
    /// Where the git repo for this project must reside.  Paths are embedded in binaries by rustc.
    pub required_git_home: String,
    /// s3 bucket location
    pub bucket: String,
    /// prefix to files uploaded in to s3
    pub prefix: Option<String>,
    /// utility of this seems to change in stable/vs rusoto, left for completeness.
    pub endpoint: Option<String>,
    /// AWS region of the bucket if necessary.
    pub region: Option<String>,
    /// Only used in stable, sscache delete when rusoto is merged in
    pub ssl: Option<bool>,
    /// If the bucket is public
    pub public: Option<bool>,
    /// Extra environment variables to set for the sccache server.
    pub envs: Option<Vec<(String, String)>>,
}
///
/// These can be passed to the installer.rs, which can check the installation against the version number supplied,
/// or install the cargo tool via either githash/repo if provided or with simply the version if the artifact is released
/// to crates.io.
///
/// Unfortunately there is no gaurantee that the installation is correct if the version numbers match as the githash
/// is not stored by default in the version number.
///
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoInstallation {
    /// The version string that must match the installation, otherwise a fresh installation will occure.
    pub version: String,
    /// Overrides the default install with a specific git repo. git-rev is required.
    pub git: Option<String>,
    /// only used if the git url is set.  This is the full git hash.
    pub git_rev: Option<String>,
    /// features to enable in the installation.
    pub features: Option<Vec<String>>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Package {
    /// Path to the crate from root
    path: PathBuf,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct SummariesConfig {
    /// Config for default members and subsets
    pub default: CargoOptionsSummary,
    /// Config for the full workspace
    pub full: CargoOptionsSummary,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct WorkspaceConfig {
    /// Allowed characters in file paths. Regex must have ^ and $ anchors.
    pub allowed_paths: String,
    /// Attributes to enforce on workspace crates
    pub enforced_attributes: EnforcedAttributesConfig,
    /// Banned dependencies
    pub banned_deps: BannedDepsConfig,
    /// Overlay config in this workspace
    pub overlay: OverlayConfig,
    /// Test-only config in this workspace
    pub test_only: TestOnlyConfig,
    /// Exceptions to whitespace linters
    pub whitespace_exceptions: Vec<String>,
    /// Subsets of this workspace
    pub subsets: BTreeMap<String, SubsetConfig>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct EnforcedAttributesConfig {
    /// Ensure the authors of every workspace crate are set to this.
    pub authors: Option<Vec<String>>,
    /// Ensure the `license` field of every workspace crate is set to this.
    pub license: Option<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct BannedDepsConfig {
    /// Banned direct dependencies
    pub direct: HashMap<String, String>,
    /// Banned dependencies in the default build set
    pub default_build: HashMap<String, String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct OverlayConfig {
    /// A list of overlay feature names
    pub features: Vec<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct TestOnlyConfig {
    /// A list of test-only workspace names
    pub members: Vec<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct SubsetConfig {
    /// The members in this subset
    pub members: HashSet<PathBuf>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Clippy {
    allowed: Vec<String>,
    warn: Vec<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Fix {}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoConfig {
    pub toolchain: String,
    pub flags: Option<String>,
    pub sccache: Option<Sccache>,
}

impl Config {
    pub fn from_file(f: impl AsRef<Path>) -> Result<Self> {
        let contents = fs::read(f)?;
        Self::from_toml(&contents).map_err(Into::into)
    }

    pub fn from_toml(bytes: &[u8]) -> Result<Self> {
        toml::from_slice(bytes).map_err(Into::into)
    }

    pub fn from_project_root() -> Result<Self> {
        Self::from_file(project_root().join("x.toml"))
    }

    pub fn cargo_config(&self) -> &CargoConfig {
        &self.cargo
    }

    pub fn system_tests(&self) -> &HashMap<String, Package> {
        &self.system_tests
    }

    pub fn summaries_config(&self) -> &SummariesConfig {
        &self.summaries
    }

    pub fn workspace_config(&self) -> &WorkspaceConfig {
        &self.workspace
    }

    pub fn allowed_clippy_lints(&self) -> &[String] {
        &self.clippy.allowed
    }

    pub fn warn_clippy_lints(&self) -> &[String] {
        &self.clippy.warn
    }

    pub fn tools(&self) -> Vec<(String, CargoInstallation)> {
        let mut tools = vec![("grcov".to_owned(), self.grcov().installer.to_owned())];
        if let Some(sccache) = &self.cargo_config().sccache {
            tools.push(("sccache".to_owned(), sccache.installer.to_owned()));
        }
        tools
    }

    pub fn grcov(&self) -> &CargoTool {
        &self.grcov
    }

    pub fn determinator_rules(&self) -> &DeterminatorRules {
        &self.determinator
    }
}
<commit_msg>[x] improve error messages while loading config<commit_after>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{utils::project_root, Result};
use anyhow::Context;
use determinator::rules::DeterminatorRules;
use guppy::graph::summaries::CargoOptionsSummary;
use serde::{Deserialize, Serialize};
use std::{
    collections::{BTreeMap, HashMap, HashSet},
    fs,
    path::{Path, PathBuf},
};

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    /// Package exceptions which need to be run special
    system_tests: HashMap<String, Package>,
    /// Configuration for generating summaries
    summaries: SummariesConfig,
    /// Workspace configuration
    workspace: WorkspaceConfig,
    /// Clippy configureation
    clippy: Clippy,
    /// Fix configureation
    fix: Fix,
    /// Cargo configuration
    cargo: CargoConfig,
    grcov: CargoTool,
    /// Determinator configuration
    determinator: DeterminatorRules,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoTool {
    pub installer: CargoInstallation,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Sccache {
    /// Sccache Url
    pub installer: CargoInstallation,
    /// Where cargo home must reside for sccache to function properly.  Paths are embedded in binaries by rustc.
    pub required_cargo_home: String,
    /// Where the git repo for this project must reside.  Paths are embedded in binaries by rustc.
    pub required_git_home: String,
    /// s3 bucket location
    pub bucket: String,
    /// prefix to files uploaded in to s3
    pub prefix: Option<String>,
    /// utility of this seems to change in stable/vs rusoto, left for completeness.
    pub endpoint: Option<String>,
    /// AWS region of the bucket if necessary.
    pub region: Option<String>,
    /// Only used in stable, sscache delete when rusoto is merged in
    pub ssl: Option<bool>,
    /// If the bucket is public
    pub public: Option<bool>,
    /// Extra environment variables to set for the sccache server.
    pub envs: Option<Vec<(String, String)>>,
}
///
/// These can be passed to the installer.rs, which can check the installation against the version number supplied,
/// or install the cargo tool via either githash/repo if provided or with simply the version if the artifact is released
/// to crates.io.
///
/// Unfortunately there is no gaurantee that the installation is correct if the version numbers match as the githash
/// is not stored by default in the version number.
///
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoInstallation {
    /// The version string that must match the installation, otherwise a fresh installation will occure.
    pub version: String,
    /// Overrides the default install with a specific git repo. git-rev is required.
    pub git: Option<String>,
    /// only used if the git url is set.  This is the full git hash.
    pub git_rev: Option<String>,
    /// features to enable in the installation.
    pub features: Option<Vec<String>>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Package {
    /// Path to the crate from root
    path: PathBuf,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct SummariesConfig {
    /// Config for default members and subsets
    pub default: CargoOptionsSummary,
    /// Config for the full workspace
    pub full: CargoOptionsSummary,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct WorkspaceConfig {
    /// Allowed characters in file paths. Regex must have ^ and $ anchors.
    pub allowed_paths: String,
    /// Attributes to enforce on workspace crates
    pub enforced_attributes: EnforcedAttributesConfig,
    /// Banned dependencies
    pub banned_deps: BannedDepsConfig,
    /// Overlay config in this workspace
    pub overlay: OverlayConfig,
    /// Test-only config in this workspace
    pub test_only: TestOnlyConfig,
    /// Exceptions to whitespace linters
    pub whitespace_exceptions: Vec<String>,
    /// Subsets of this workspace
    pub subsets: BTreeMap<String, SubsetConfig>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct EnforcedAttributesConfig {
    /// Ensure the authors of every workspace crate are set to this.
    pub authors: Option<Vec<String>>,
    /// Ensure the `license` field of every workspace crate is set to this.
    pub license: Option<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct BannedDepsConfig {
    /// Banned direct dependencies
    pub direct: HashMap<String, String>,
    /// Banned dependencies in the default build set
    pub default_build: HashMap<String, String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct OverlayConfig {
    /// A list of overlay feature names
    pub features: Vec<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct TestOnlyConfig {
    /// A list of test-only workspace names
    pub members: Vec<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct SubsetConfig {
    /// The members in this subset
    pub members: HashSet<PathBuf>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Clippy {
    allowed: Vec<String>,
    warn: Vec<String>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Fix {}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoConfig {
    pub toolchain: String,
    pub flags: Option<String>,
    pub sccache: Option<Sccache>,
}

impl Config {
    pub fn from_file(f: impl AsRef<Path>) -> Result<Self> {
        let f = f.as_ref();
        let contents =
            fs::read(f).with_context(|| format!("could not read config file {}", f.display()))?;
        Self::from_toml(&contents)
            .with_context(|| format!("could not parse config file {}", f.display()))
    }

    pub fn from_toml(bytes: &[u8]) -> Result<Self> {
        toml::from_slice(bytes).map_err(Into::into)
    }

    pub fn from_project_root() -> Result<Self> {
        Self::from_file(project_root().join("x.toml"))
    }

    pub fn cargo_config(&self) -> &CargoConfig {
        &self.cargo
    }

    pub fn system_tests(&self) -> &HashMap<String, Package> {
        &self.system_tests
    }

    pub fn summaries_config(&self) -> &SummariesConfig {
        &self.summaries
    }

    pub fn workspace_config(&self) -> &WorkspaceConfig {
        &self.workspace
    }

    pub fn allowed_clippy_lints(&self) -> &[String] {
        &self.clippy.allowed
    }

    pub fn warn_clippy_lints(&self) -> &[String] {
        &self.clippy.warn
    }

    pub fn tools(&self) -> Vec<(String, CargoInstallation)> {
        let mut tools = vec![("grcov".to_owned(), self.grcov().installer.to_owned())];
        if let Some(sccache) = &self.cargo_config().sccache {
            tools.push(("sccache".to_owned(), sccache.installer.to_owned()));
        }
        tools
    }

    pub fn grcov(&self) -> &CargoTool {
        &self.grcov
    }

    pub fn determinator_rules(&self) -> &DeterminatorRules {
        &self.determinator
    }
}
<|endoftext|>