<commit_before>extern crate piston;
extern crate graphics;
extern crate opengl_graphics;
extern crate rand;
extern crate rpsrtsrs;
#[cfg(feature = "include_sdl2")] extern crate sdl2_window;
#[cfg(feature = "include_glfw")] extern crate glfw_window;
#[cfg(feature = "include_glutin")] extern crate glutin_window;

use std::f64::consts::PI;
use piston::window::WindowSettings;
use opengl_graphics::{ GlGraphics, OpenGL };
use piston::input::*;
use piston::event_loop::*;
#[cfg(feature = "include_sdl2")] use sdl2_window::Sdl2Window as Window;
#[cfg(feature = "include_glfw")] use glfw_window::GlfwWindow as Window;
#[cfg(feature = "include_glutin")] use glutin_window::GlutinWindow as Window;
use rpsrtsrs::shapes::Unit;

pub struct App {
    gl: GlGraphics, // OpenGL drawing backend.
    units: Vec<Unit>,
}

impl App {
    fn select(&mut self, position: &[f64;2]) {
        for s in &mut self.units {
            s.selected = position[0]< s.position[0]+25.0 &&
                position[0]> s.position[0]-25.0 &&
                position[1]< s.position[1]+25.0 &&
                position[1]> s.position[1]-25.0;
        };
    }

    fn render(&mut self, args: &RenderArgs) {
        use graphics::{polygon, clear};
        use graphics::Transformed;
        use graphics::types::Polygon;

        const BLACK:  [f32; 4] = [0.0, 0.0,  0.0,  1.0];
        const YELLOW: [f32; 4] = [1.0, 1.0,  0.22, 1.0];
        const ORANGE: [f32; 4] = [1.0, 0.61, 0.22, 1.0];

        const UNIT_SIZE: f64 = 50.0;
        const FRONT_THICKNESS: f64 = 5.0;

        let units = &self.units;

        self.gl.draw(args.viewport(), |c, gl| {

            // Clear the screen.
            clear(BLACK, gl);

            for s in units.iter() {

                // Create a triangle polygon. The initial orientation is facing east.
                let triangle: Polygon = &[
                    [0.0, UNIT_SIZE / 2.0], // Left
                    [UNIT_SIZE, UNIT_SIZE], // Top right
                    [UNIT_SIZE, 0.0],       // Bottom right
                ];

                // Create a border on the front of the polygon. This is a trapezoid.
                // Because the angle of the trapezoid side is 22.5°, we know that `dx` is always `2 * dy`.
                let front: Polygon = &[
                    [UNIT_SIZE, UNIT_SIZE],                                           // Top right
                    [UNIT_SIZE, 0.0],                                                 // Bottom right
                    [UNIT_SIZE - FRONT_THICKNESS, FRONT_THICKNESS / 2.0],             // Bottom left
                    [UNIT_SIZE - FRONT_THICKNESS, UNIT_SIZE - FRONT_THICKNESS / 2.0], // Top left
                ];

                // Create the transformation matrix
                let transform = c.transform.trans(s.position[0], s.position[1])
                    .rot_rad(s.rotation)
                    .trans(-25.0, -25.0);

                // Draw the unit ORANGE if selected
                if s.selected {
                    polygon(ORANGE, triangle, transform, gl);
                    polygon(YELLOW, front, transform, gl);
                } else {
                    polygon(YELLOW, triangle, transform, gl);
                    polygon(ORANGE, front, transform, gl);
                }

            }
        });
    }

    fn update(&mut self, args: &UpdateArgs) {
        for s in &mut self.units {
            let diff = [s.target[0]-s.position[0], s.target[1]-s.position[1]];
            s.position[0] += diff[0]/2.0*args.dt;
            s.position[1] += diff[1]/2.0*args.dt;
        }
    }

    fn move_selected(&mut self, position: [f64;2]) {
        for s in &mut self.units {
            if s.selected {
                s.target = position;
                let dx = position[0] - s.position[0];
                let dy = position[1] - s.position[1];
                if dx.is_sign_negative() {
                    s.rotation = (dy / dx).atan() + PI;
                } else {
                    s.rotation = (dy / dx).atan();
                }
                println!("dx: {}, dy: {}, new rotation: {}", dx, dy, s.rotation);
            }
        }
    }
}

fn main() {
    let opengl = OpenGL::V3_2;

    // Create an Glutin window.
    let window : Window = WindowSettings::new(
        "rpsrtsrs",
        [640, 480]
    ).exit_on_esc(true).into();

    // Create a new game and run it.
    let mut app = App {
        gl: GlGraphics::new(opengl),
        units: vec![],
    };
    for _ in 0..10 {
        // Create new unit in random location
        let x = rand::random::<f64>() * 600.0 + 40.0;
        let y = rand::random::<f64>() * 440.0 + 40.0;
        let r = (rand::random::<f64>() - 0.5) * PI;
        let unit = Unit::new([x,y], r);

        // Register unit
        app.units.push(unit);
    }

    let mut cursor = [0.0,0.0];

    for e in window.events() {
        if let Some(r) = e.render_args() {
            app.render(&r);
        }
        if let Some(Button::Mouse(button)) = e.press_args() {
            match button {
                MouseButton::Left  => app.select(&cursor),
                MouseButton::Right => app.move_selected(cursor),
                _ => println!("Pressed mouse button '{:?}'", button),
            }
        }
        e.mouse_cursor(|x, y| {
            cursor = [x, y];
        });

        if let Some(u) = e.update_args() {
            app.update(&u);
        }
    }
}
<commit_msg>Enable antialiasing (8 samples)<commit_after>extern crate piston;
extern crate graphics;
extern crate opengl_graphics;
extern crate rand;
extern crate rpsrtsrs;
#[cfg(feature = "include_sdl2")] extern crate sdl2_window;
#[cfg(feature = "include_glfw")] extern crate glfw_window;
#[cfg(feature = "include_glutin")] extern crate glutin_window;

use std::f64::consts::PI;
use piston::window::WindowSettings;
use opengl_graphics::{ GlGraphics, OpenGL };
use piston::input::*;
use piston::event_loop::*;
#[cfg(feature = "include_sdl2")] use sdl2_window::Sdl2Window as Window;
#[cfg(feature = "include_glfw")] use glfw_window::GlfwWindow as Window;
#[cfg(feature = "include_glutin")] use glutin_window::GlutinWindow as Window;
use rpsrtsrs::shapes::Unit;

pub struct App {
    gl: GlGraphics, // OpenGL drawing backend.
    units: Vec<Unit>,
}

impl App {
    fn select(&mut self, position: &[f64;2]) {
        for s in &mut self.units {
            s.selected = position[0]< s.position[0]+25.0 &&
                position[0]> s.position[0]-25.0 &&
                position[1]< s.position[1]+25.0 &&
                position[1]> s.position[1]-25.0;
        };
    }

    fn render(&mut self, args: &RenderArgs) {
        use graphics::{polygon, clear};
        use graphics::Transformed;
        use graphics::types::Polygon;

        const BLACK:  [f32; 4] = [0.0, 0.0,  0.0,  1.0];
        const YELLOW: [f32; 4] = [1.0, 1.0,  0.22, 1.0];
        const ORANGE: [f32; 4] = [1.0, 0.61, 0.22, 1.0];

        const UNIT_SIZE: f64 = 50.0;
        const FRONT_THICKNESS: f64 = 5.0;

        let units = &self.units;

        self.gl.draw(args.viewport(), |c, gl| {

            // Clear the screen.
            clear(BLACK, gl);

            for s in units.iter() {

                // Create a triangle polygon. The initial orientation is facing east.
                let triangle: Polygon = &[
                    [0.0, UNIT_SIZE / 2.0], // Left
                    [UNIT_SIZE, UNIT_SIZE], // Top right
                    [UNIT_SIZE, 0.0],       // Bottom right
                ];

                // Create a border on the front of the polygon. This is a trapezoid.
                // Because the angle of the trapezoid side is 22.5°, we know that `dx` is always `2 * dy`.
                let front: Polygon = &[
                    [UNIT_SIZE, UNIT_SIZE],                                           // Top right
                    [UNIT_SIZE, 0.0],                                                 // Bottom right
                    [UNIT_SIZE - FRONT_THICKNESS, FRONT_THICKNESS / 2.0],             // Bottom left
                    [UNIT_SIZE - FRONT_THICKNESS, UNIT_SIZE - FRONT_THICKNESS / 2.0], // Top left
                ];

                // Create the transformation matrix
                let transform = c.transform.trans(s.position[0], s.position[1])
                    .rot_rad(s.rotation)
                    .trans(-25.0, -25.0);

                // Draw the unit ORANGE if selected
                if s.selected {
                    polygon(ORANGE, triangle, transform, gl);
                    polygon(YELLOW, front, transform, gl);
                } else {
                    polygon(YELLOW, triangle, transform, gl);
                    polygon(ORANGE, front, transform, gl);
                }

            }
        });
    }

    fn update(&mut self, args: &UpdateArgs) {
        for s in &mut self.units {
            let diff = [s.target[0]-s.position[0], s.target[1]-s.position[1]];
            s.position[0] += diff[0]/2.0*args.dt;
            s.position[1] += diff[1]/2.0*args.dt;
        }
    }

    fn move_selected(&mut self, position: [f64;2]) {
        for s in &mut self.units {
            if s.selected {
                s.target = position;
                let dx = position[0] - s.position[0];
                let dy = position[1] - s.position[1];
                if dx.is_sign_negative() {
                    s.rotation = (dy / dx).atan() + PI;
                } else {
                    s.rotation = (dy / dx).atan();
                }
                println!("dx: {}, dy: {}, new rotation: {}", dx, dy, s.rotation);
            }
        }
    }
}

fn main() {
    let opengl = OpenGL::V3_2;

    // Create an Glutin window.
    let window : Window = WindowSettings::new(
        "rpsrtsrs",
        [640, 480]
    ).exit_on_esc(true).samples(8).into();

    // Create a new game and run it.
    let mut app = App {
        gl: GlGraphics::new(opengl),
        units: vec![],
    };
    for _ in 0..10 {
        // Create new unit in random location
        let x = rand::random::<f64>() * 600.0 + 40.0;
        let y = rand::random::<f64>() * 440.0 + 40.0;
        let r = (rand::random::<f64>() - 0.5) * PI;
        let unit = Unit::new([x,y], r);

        // Register unit
        app.units.push(unit);
    }

    let mut cursor = [0.0,0.0];

    for e in window.events() {
        if let Some(r) = e.render_args() {
            app.render(&r);
        }
        if let Some(Button::Mouse(button)) = e.press_args() {
            match button {
                MouseButton::Left  => app.select(&cursor),
                MouseButton::Right => app.move_selected(cursor),
                _ => println!("Pressed mouse button '{:?}'", button),
            }
        }
        e.mouse_cursor(|x, y| {
            cursor = [x, y];
        });

        if let Some(u) = e.update_args() {
            app.update(&u);
        }
    }
}
<|endoftext|>


<commit_before>extern crate rustpn;

use rustpn::parse::parse;
use rustpn::vm::{self, Vm, Method};
use rustpn::item::{Stack, StackItem};
use std::rc::Rc;
use std::error::Error;
use std::os;

fn pop_integer(stack: &mut Stack) -> vm::Result<i64> {
    match stack.pop() {
        Some(StackItem::Integer(i)) => Ok(i),
        Some(..) => Err(vm::Error::TypeError),
        None => Err(vm::Error::StackUnderflow),
    }
}

fn main() {
    let mut vm = Vm::new();
    vm.methods.insert(String::from_str("+"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            vm.stack.push(StackItem::Integer(n1 + n2));
            Ok(())
        })));
    vm.methods.insert(String::from_str("-"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            vm.stack.push(StackItem::Integer(n1 - n2));
            Ok(())
        })));
    vm.methods.insert(String::from_str("*"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            vm.stack.push(StackItem::Integer(n1 * n2));
            Ok(())
        })));
    vm.methods.insert(String::from_str("/"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            match n2 {
                0 => return Err(vm::Error::DivideByZero),
                _ => vm.stack.push(StackItem::Integer(n1 / n2)),
            }
            Ok(())
        })));
    for program in os::args().iter().skip(1) {
        print!("program: {{{}}} => ", program);
        match vm.run_block(&parse(program.as_slice()).unwrap()) {
            Ok(()) => println!("stack: {:?}", vm.stack),
            Err(e) => println!("error: {}", e.description()),
        }
    }
}
<commit_msg>Handle parser errors in binary<commit_after>extern crate rustpn;

use rustpn::parse::parse;
use rustpn::vm::{self, Vm, Method};
use rustpn::item::{Stack, StackItem};
use std::rc::Rc;
use std::error::Error;
use std::os;

fn pop_integer(stack: &mut Stack) -> vm::Result<i64> {
    match stack.pop() {
        Some(StackItem::Integer(i)) => Ok(i),
        Some(..) => Err(vm::Error::TypeError),
        None => Err(vm::Error::StackUnderflow),
    }
}

fn main() {
    let mut vm = Vm::new();
    vm.methods.insert(String::from_str("+"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            vm.stack.push(StackItem::Integer(n1 + n2));
            Ok(())
        })));
    vm.methods.insert(String::from_str("-"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            vm.stack.push(StackItem::Integer(n1 - n2));
            Ok(())
        })));
    vm.methods.insert(String::from_str("*"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            vm.stack.push(StackItem::Integer(n1 * n2));
            Ok(())
        })));
    vm.methods.insert(String::from_str("/"),
        Rc::new(Method::Builtin(box |vm| {
            let n2 = try!(pop_integer(&mut vm.stack));
            let n1 = try!(pop_integer(&mut vm.stack));
            match n2 {
                0 => return Err(vm::Error::DivideByZero),
                _ => vm.stack.push(StackItem::Integer(n1 / n2)),
            }
            Ok(())
        })));
    vm.methods.insert(String::from_str("fn"),
        Rc::new(Method::Builtin(box |vm| {
            let block = try!(vm.stack.pop().ok_or(vm::Error::StackUnderflow));
            let name = try!(vm.stack.pop().ok_or(vm::Error::StackUnderflow));
            match (name, block) {
                (StackItem::String(s), StackItem::Block(b)) =>
                    { vm.methods.insert(s, Rc::new(Method::Block(b))); },
                _ => return Err(vm::Error::TypeError),
            }
            Ok(())
        })));
    for program in os::args().iter().skip(1) {
        print!("program: {{{}}} => ", program);
        match parse(program.as_slice()) {
            Ok(ref p) => match vm.run_block(p) {
                Ok(()) => println!("stack: {:?}", vm.stack),
                Err(e) => println!("runtime error: {}", e.description()),
            },
            Err(e) => println!("parser error: {}", e.description()),
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fs;
use std::fs::{canonicalize, read_dir};
use std::path::{Path, PathBuf};

use language_tags::LanguageTag;

use layout::Layout;
use project::Project;
use serde_yaml::Value;
use target::Targets;

use self::resources::Resources;

pub mod layout;
pub(crate) mod project;
pub(crate) mod resources;
pub(crate) mod target;

const PROJECT_FILENAME: &str = "project.yaml";
const LAYOUTS_FOLDER: &str = "layouts";
const TARGETS_FOLDER: &str = "targets";
const RESOURCES_FOLDER: &str = "resources";

const YAML_EXT: &str = "yaml";

pub const DEFAULT_DECIMAL: &str = ".";
const COMMA_DECIMAL: &str = ",";

#[derive(Debug)]
pub struct KbdgenBundle {
    pub path: PathBuf,
    pub project: Project,
    pub layouts: HashMap<LanguageTag, Layout>,
    pub targets: Targets,
    pub resources: Resources,
}

impl KbdgenBundle {
    pub fn name(&self) -> &str {
        self.path
            .file_stem()
            .expect("No file stem?")
            .to_str()
            .expect("Must be valid utf-8")
    }
}

pub fn read_kbdgen_bundle(path: &Path) -> Result<KbdgenBundle, Error> {
    let canonical_bundle_path: PathBuf = canonicalize(path)?;

    tracing::info!("Canonical Bundle Path: {:?}", &canonical_bundle_path);

    let project: Project = serde_yaml::from_str(&fs::read_to_string(
        canonical_bundle_path.join(PROJECT_FILENAME),
    )?)?;

    let layouts_path = canonical_bundle_path.join(LAYOUTS_FOLDER);
    let targets_path = canonical_bundle_path.join(TARGETS_FOLDER);
    let resources_path = canonical_bundle_path.join(RESOURCES_FOLDER);

    let layouts = read_layouts(&layouts_path)?;
    let targets = read_targets(&targets_path)?;
    let resources = read_resources(&resources_path)?;

    Ok(KbdgenBundle {
        path: canonical_bundle_path,
        project,
        layouts,
        targets,
        resources,
    })
}

fn read_layouts(path: &Path) -> Result<HashMap<LanguageTag, Layout>, Error> {
    read_dir(path)?
        .filter_map(Result::ok)
        .map(|file| file.path())
        .filter(|path| path.is_file())
        .filter(|path| match path.extension() {
            Some(ext) => ext == YAML_EXT,
            None => false,
        })
        .map(|path| {
            let tag = path
                .file_stem()
                .ok_or_else(|| Error::NoFileStem { path: path.clone() })?
                .to_string_lossy();

            let tag: LanguageTag = tag.parse().map_err(|_| Error::InvalidLanguageTag {
                tag: tag.to_string(),
            })?;

            let mut yaml: Value = serde_yaml::from_str(&fs::read_to_string(path)?)?;
            yaml.as_mapping_mut()
                .expect("top level yaml type must be a mapping")
                .insert(
                    Value::String("languageTag".to_owned()),
                    Value::String(tag.to_string()),
                );

            let mut layout: Layout = serde_yaml::from_value(yaml)?;

            let _autonym = layout
                .display_names
                .get(&tag.primary_language().parse::<LanguageTag>().unwrap())
                .unwrap_or_else(|| {
                    panic!("displayNames for the layout do not have the autonym");
                });

            if let Some(decimal) = layout.decimal.as_ref() {
                if decimal != COMMA_DECIMAL && decimal != DEFAULT_DECIMAL {
                    tracing::error!(
                        "{} is not supported as a decimal character, setting to {}",
                        decimal,
                        DEFAULT_DECIMAL
                    );
                    layout.decimal = Some(DEFAULT_DECIMAL.to_owned());
                }
            };

            Ok((tag, layout))
        })
        .collect()
}

fn load_yaml<T>(path: &Path) -> Option<T>
where
    T: for<'de> serde::Deserialize<'de>,
{
    let s = match fs::read_to_string(path) {
        Ok(v) => v,
        Err(e) => {
            tracing::error!(error = ?e, "Failed to read file to string");
            return None;
        }
    };

    match serde_yaml::from_str::<T>(&s) {
        Ok(v) => Some(v),
        Err(e) => {
            tracing::error!(error = ?e, "Error parsing YAML");
            None
        }
    }
}

fn read_resources(path: &Path) -> Result<Resources, Error> {
    let mut resources = Resources::default();

    let iter = read_dir(path)?
        .filter_map(Result::ok)
        .map(|file| file.path())
        .filter(|path| path.is_dir());

    for path in iter {
        let target_name = path.file_name().map(|x| x.to_string_lossy()).unwrap();

        match target_name.as_ref() {
            "macos" => {
                resources.macos = resources::MacOS::load(&path).ok();
            }
            "chromeos" => {
                resources.chromeos = resources::ChromeOS::load(&path).ok();
            }
            name => {
                tracing::warn!("Saw target with name {name} but did not parse");
                continue;
            }
        };
    }
    Ok(resources)
}

fn read_targets(path: &Path) -> Result<Targets, Error> {
    let mut targets = Targets::default();

    let iter = read_dir(path)?
        .filter_map(Result::ok)
        .map(|file| file.path())
        .filter(|path| path.is_file())
        .filter(|path| match path.extension() {
            Some(ext) => ext == YAML_EXT,
            None => false,
        });

    for path in iter {
        let target_name = path
            .file_stem()
            .ok_or_else(|| Error::NoFileStem { path: path.clone() })
            .map(|x| x.to_string_lossy())?;

        match target_name.as_ref() {
            "windows" => {
                targets.windows = load_yaml(&path);
            }
            "ios" => {
                targets.ios = load_yaml(&path);
            }
            "macos" => {
                targets.macos = load_yaml(&path);
            }
            "chromeos" => targets.chromeos = load_yaml(&path),
            name => {
                tracing::warn!("Saw target with name {name} but did not parse");
                continue;
            }
        };
    }

    Ok(targets)
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("{0}")]
    IoError(#[from] std::io::Error),

    #[error("{0}")]
    YamlError(#[from] serde_yaml::Error),

    #[error(".yaml files must have a stem, failed to parse: `{}`", path.display())]
    NoFileStem { path: PathBuf },

    #[error("Failed to parse language tag: `{}`", tag)]
    InvalidLanguageTag { tag: String },
}
<commit_msg>fix naming<commit_after>use std::collections::HashMap;
use std::fs;
use std::fs::{canonicalize, read_dir};
use std::path::{Path, PathBuf};

use language_tags::LanguageTag;

use layout::Layout;
use project::Project;
use serde_yaml::Value;
use target::Targets;

use self::resources::Resources;

pub mod layout;
pub(crate) mod project;
pub(crate) mod resources;
pub(crate) mod target;

const PROJECT_FILENAME: &str = "project.yaml";
const LAYOUTS_FOLDER: &str = "layouts";
const TARGETS_FOLDER: &str = "targets";
const RESOURCES_FOLDER: &str = "resources";

const YAML_EXT: &str = "yaml";

pub const DEFAULT_DECIMAL: &str = ".";
const COMMA_DECIMAL: &str = ",";

#[derive(Debug)]
pub struct KbdgenBundle {
    pub path: PathBuf,
    pub project: Project,
    pub layouts: HashMap<LanguageTag, Layout>,
    pub targets: Targets,
    pub resources: Resources,
}

impl KbdgenBundle {
    pub fn name(&self) -> &str {
        self.path
            .file_stem()
            .expect("No file stem?")
            .to_str()
            .expect("Must be valid utf-8")
    }
}

pub fn read_kbdgen_bundle(path: &Path) -> Result<KbdgenBundle, Error> {
    let canonical_bundle_path: PathBuf = canonicalize(path)?;

    tracing::info!("Canonical Bundle Path: {:?}", &canonical_bundle_path);

    let project: Project = serde_yaml::from_str(&fs::read_to_string(
        canonical_bundle_path.join(PROJECT_FILENAME),
    )?)?;

    let layouts_path = canonical_bundle_path.join(LAYOUTS_FOLDER);
    let targets_path = canonical_bundle_path.join(TARGETS_FOLDER);
    let resources_path = canonical_bundle_path.join(RESOURCES_FOLDER);

    let layouts = read_layouts(&layouts_path)?;
    let targets = read_targets(&targets_path)?;
    let resources = read_resources(&resources_path)?;

    Ok(KbdgenBundle {
        path: canonical_bundle_path,
        project,
        layouts,
        targets,
        resources,
    })
}

fn read_layouts(path: &Path) -> Result<HashMap<LanguageTag, Layout>, Error> {
    read_dir(path)?
        .filter_map(Result::ok)
        .map(|file| file.path())
        .filter(|path| path.is_file())
        .filter(|path| match path.extension() {
            Some(ext) => ext == YAML_EXT,
            None => false,
        })
        .map(|path| {
            let tag = path
                .file_stem()
                .ok_or_else(|| Error::NoFileStem { path: path.clone() })?
                .to_string_lossy();

            let tag: LanguageTag = tag.parse().map_err(|_| Error::InvalidLanguageTag {
                tag: tag.to_string(),
            })?;

            let mut yaml: Value = serde_yaml::from_str(&fs::read_to_string(path)?)?;
            yaml.as_mapping_mut()
                .expect("top level yaml type must be a mapping")
                .insert(
                    Value::String("languageTag".to_owned()),
                    Value::String(tag.to_string()),
                );

            let mut layout: Layout = serde_yaml::from_value(yaml)?;

            let _autonym = layout
                .display_names
                .get(&tag.primary_language().parse::<LanguageTag>().unwrap())
                .unwrap_or_else(|| {
                    panic!("displayNames for the layout do not have the autonym");
                });

            if let Some(decimal) = layout.decimal.as_ref() {
                if decimal != COMMA_DECIMAL && decimal != DEFAULT_DECIMAL {
                    tracing::error!(
                        "{} is not supported as a decimal character, setting to {}",
                        decimal,
                        DEFAULT_DECIMAL
                    );
                    layout.decimal = Some(DEFAULT_DECIMAL.to_owned());
                }
            };

            Ok((tag, layout))
        })
        .collect()
}

fn load_yaml<T>(path: &Path) -> Option<T>
where
    T: for<'de> serde::Deserialize<'de>,
{
    let s = match fs::read_to_string(path) {
        Ok(v) => v,
        Err(e) => {
            tracing::error!(error = ?e, "Failed to read file to string");
            return None;
        }
    };

    match serde_yaml::from_str::<T>(&s) {
        Ok(v) => Some(v),
        Err(e) => {
            tracing::error!(error = ?e, "Error parsing YAML");
            None
        }
    }
}

fn read_resources(path: &Path) -> Result<Resources, Error> {
    let mut resources = Resources::default();

    let iter = read_dir(path)?
        .filter_map(Result::ok)
        .map(|file| file.path())
        .filter(|path| path.is_dir());

    for path in iter {
        let target_name = path.file_name().map(|x| x.to_string_lossy()).unwrap();

        match target_name.as_ref() {
            "macos" => {
                resources.macos = resources::MacOS::load(&path).ok();
            }
            "chromeos" => {
                resources.chromeos = resources::ChromeOS::load(&path).ok();
            }
            name => {
                tracing::warn!("Saw resource folder with name {name} but did not parse");
                continue;
            }
        };
    }
    Ok(resources)
}

fn read_targets(path: &Path) -> Result<Targets, Error> {
    let mut targets = Targets::default();

    let iter = read_dir(path)?
        .filter_map(Result::ok)
        .map(|file| file.path())
        .filter(|path| path.is_file())
        .filter(|path| match path.extension() {
            Some(ext) => ext == YAML_EXT,
            None => false,
        });

    for path in iter {
        let target_name = path
            .file_stem()
            .ok_or_else(|| Error::NoFileStem { path: path.clone() })
            .map(|x| x.to_string_lossy())?;

        match target_name.as_ref() {
            "windows" => {
                targets.windows = load_yaml(&path);
            }
            "ios" => {
                targets.ios = load_yaml(&path);
            }
            "macos" => {
                targets.macos = load_yaml(&path);
            }
            "chromeos" => targets.chromeos = load_yaml(&path),
            name => {
                tracing::warn!("Saw target with name {name} but did not parse");
                continue;
            }
        };
    }

    Ok(targets)
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("{0}")]
    IoError(#[from] std::io::Error),

    #[error("{0}")]
    YamlError(#[from] serde_yaml::Error),

    #[error(".yaml files must have a stem, failed to parse: `{}`", path.display())]
    NoFileStem { path: PathBuf },

    #[error("Failed to parse language tag: `{}`", tag)]
    InvalidLanguageTag { tag: String },
}
<|endoftext|>


<commit_before>/*
 * Copyright 2017-2018 Ben Ashford
 *
 * 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 client API itself.
//!
//! This contains three main functions that return three specific types of client:
//!
//! * `connect` returns a pair of `Stream` and `Sink`, clients can write RESP messages to the
//! `Sink` and read RESP messages from the `Stream`. Pairing requests to responses is up to the
//! client.  This is intended to be a low-level interface from which more user-friendly interfaces
//! can be built.
//! * `paired_connect` is used for most of the standard Redis commands, where one request results
//! in one response.
//! * `pubsub_connect` is used for Redis's PUBSUB functionality.

pub mod connect;
#[macro_use]
pub mod paired;
pub mod pubsub;

pub use self::connect::{connect, ClientConnection};
// TODO - uncomment
// pub use self::paired::{paired_connect, PairedConnection};
// pub use self::pubsub::{pubsub_connect, PubsubConnection};

#[cfg(test)]
mod test {
    use std::io;

    use futures::{stream, Future, Sink, Stream};

    use error;
    use resp;

    // TODO - uncomment
    // #[test]
    // fn can_connect() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let connection = super::connect(&addr)
    //         .map_err(|e| e.into())
    //         .and_then(|connection| {
    //             let a = connection
    //                 .sender
    //                 .send(resp_array!["PING", "TEST"])
    //                 .map_err(|e| e.into());
    //             let b = connection.receiver.take(1).collect();
    //             a.join(b)
    //         });

    //     let (_, values) = core.run(connection).unwrap();
    //     assert_eq!(values.len(), 1);
    //     assert_eq!(values[0], "TEST".into());
    // }

    // #[test]
    // fn complex_test() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();
    //     let connection = super::connect(&addr)
    //         .map_err(|e| e.into())
    //         .and_then(|connection| {
    //             let mut ops = Vec::<resp::RespValue>::new();
    //             ops.push(resp_array!["FLUSH"]);
    //             ops.extend(
    //                 (0..1000).map(|i| resp_array!["SADD", "test_set", format!("VALUE: {}", i)]),
    //             );
    //             ops.push(resp_array!["SMEMBERS", "test_set"]);
    //             let send = connection
    //                 .sender
    //                 .send_all(stream::iter_ok::<_, io::Error>(ops))
    //                 .map_err(|e| e.into());
    //             let receive = connection.receiver.skip(1001).take(1).collect();
    //             send.join(receive)
    //         });
    //     let (_, values) = core.run(connection).unwrap();
    //     assert_eq!(values.len(), 1);
    //     let values = match &values[0] {
    //         &resp::RespValue::Array(ref values) => values.clone(),
    //         _ => panic!("Not an array"),
    //     };
    //     assert_eq!(values.len(), 1000);
    // }

    // #[test]
    // fn can_paired_connect() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let connect_f = super::paired_connect(&addr).and_then(|connection| {
    //         let res_f = connection.send(resp_array!["PING", "TEST"]);
    //         faf!(connection.send(resp_array!["SET", "X", "123"]));
    //         let wait_f = connection.send(resp_array!["GET", "X"]);
    //         res_f.join(wait_f)
    //     });
    //     let (result_1, result_2): (String, String) = core.run(connect_f).unwrap();
    //     assert_eq!(result_1, "TEST");
    //     assert_eq!(result_2, "123");
    // }

    // #[test]
    // fn complex_paired_connect() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let connect_f = super::paired_connect(&addr).and_then(|connection| {
    //         connection
    //             .send(resp_array!["INCR", "CTR"])
    //             .and_then(move |value: String| {
    //                 connection.send(resp_array!["SET", "LASTCTR", value])
    //             })
    //     });
    //     let result: String = core.run(connect_f).unwrap();
    //     assert_eq!(result, "OK");
    // }

    // #[test]
    // fn sending_a_lot_of_data_test() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let test_f = super::paired_connect(&addr);
    //     let send_data = test_f.and_then(|connection| {
    //         let mut futures = Vec::with_capacity(1000);
    //         for i in 0..1000 {
    //             let key = format!("X_{}", i);
    //             faf!(connection.send(resp_array!["SET", &key, i.to_string()]));
    //             futures.push(connection.send(resp_array!["GET", key]));
    //         }
    //         futures.remove(999)
    //     });
    //     let result: String = core.run(send_data).unwrap();
    //     assert_eq!(result, "999");
    // }

    // #[test]
    // fn pubsub_test() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();
    //     let paired_c = super::paired_connect(&addr);
    //     let pubsub_c = super::pubsub_connect(&addr);
    //     let msgs = paired_c.join(pubsub_c).and_then(|(paired, pubsub)| {
    //         let subscribe = pubsub.subscribe("test-topic");
    //         subscribe.and_then(move |msgs| {
    //             faf!(paired.send(resp_array!["PUBLISH", "test-topic", "test-message"]));
    //             faf!(paired.send(resp_array!["PUBLISH", "test-not-topic", "test-message-1.5"]));
    //             paired
    //                 .send(resp_array!["PUBLISH", "test-topic", "test-message2"])
    //                 .map(|_: resp::RespValue| msgs)
    //         })
    //     });
    //     let tst = msgs.and_then(|msgs| {
    //         msgs.take(2)
    //             .collect()
    //             .map_err(|_| error::internal("unreachable"))
    //     });
    //     let result = core.run(tst).unwrap();
    //     assert_eq!(result.len(), 2);
    //     assert_eq!(result[0], "test-message".into());
    //     assert_eq!(result[1], "test-message2".into());
    // }
}
<commit_msg>Run and extract the result from a Future<commit_after>/*
 * Copyright 2017-2018 Ben Ashford
 *
 * 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 client API itself.
//!
//! This contains three main functions that return three specific types of client:
//!
//! * `connect` returns a pair of `Stream` and `Sink`, clients can write RESP messages to the
//! `Sink` and read RESP messages from the `Stream`. Pairing requests to responses is up to the
//! client.  This is intended to be a low-level interface from which more user-friendly interfaces
//! can be built.
//! * `paired_connect` is used for most of the standard Redis commands, where one request results
//! in one response.
//! * `pubsub_connect` is used for Redis's PUBSUB functionality.

pub mod connect;
#[macro_use]
pub mod paired;
pub mod pubsub;

pub use self::connect::{connect, ClientConnection};
// TODO - uncomment
// pub use self::paired::{paired_connect, PairedConnection};
// pub use self::pubsub::{pubsub_connect, PubsubConnection};

#[cfg(test)]
mod test {
    use std::fmt;
    use std::io;

    use futures;
    use futures::{stream, Future, Sink, Stream};

    use tokio::executor::current_thread;

    use error;
    use resp;

    fn extract_result<F, R, E>(f: F) -> R
    where
        F: Future<Item = R, Error = E>,
        E: fmt::Debug,
    {
        let r = current_thread::run(|_| f.wait());
        r.expect("Future did not complete satisfactorally")
    }

    #[test]
    fn can_connect() {
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connection = super::connect(&addr)
            .map_err(|e| e.into())
            .and_then(|connection| {
                let a = connection
                    .sender
                    .send(resp_array!["PING", "TEST"])
                    .map_err(|e| e.into());
                let b = connection.receiver.take(1).collect();
                a.join(b)
            });

        let (_, values) = extract_result(connection);

        assert_eq!(values.len(), 1);
        assert_eq!(values[0], "TEST".into());
    }

    // #[test]
    // fn complex_test() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();
    //     let connection = super::connect(&addr)
    //         .map_err(|e| e.into())
    //         .and_then(|connection| {
    //             let mut ops = Vec::<resp::RespValue>::new();
    //             ops.push(resp_array!["FLUSH"]);
    //             ops.extend(
    //                 (0..1000).map(|i| resp_array!["SADD", "test_set", format!("VALUE: {}", i)]),
    //             );
    //             ops.push(resp_array!["SMEMBERS", "test_set"]);
    //             let send = connection
    //                 .sender
    //                 .send_all(stream::iter_ok::<_, io::Error>(ops))
    //                 .map_err(|e| e.into());
    //             let receive = connection.receiver.skip(1001).take(1).collect();
    //             send.join(receive)
    //         });
    //     let (_, values) = core.run(connection).unwrap();
    //     assert_eq!(values.len(), 1);
    //     let values = match &values[0] {
    //         &resp::RespValue::Array(ref values) => values.clone(),
    //         _ => panic!("Not an array"),
    //     };
    //     assert_eq!(values.len(), 1000);
    // }

    // #[test]
    // fn can_paired_connect() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let connect_f = super::paired_connect(&addr).and_then(|connection| {
    //         let res_f = connection.send(resp_array!["PING", "TEST"]);
    //         faf!(connection.send(resp_array!["SET", "X", "123"]));
    //         let wait_f = connection.send(resp_array!["GET", "X"]);
    //         res_f.join(wait_f)
    //     });
    //     let (result_1, result_2): (String, String) = core.run(connect_f).unwrap();
    //     assert_eq!(result_1, "TEST");
    //     assert_eq!(result_2, "123");
    // }

    // #[test]
    // fn complex_paired_connect() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let connect_f = super::paired_connect(&addr).and_then(|connection| {
    //         connection
    //             .send(resp_array!["INCR", "CTR"])
    //             .and_then(move |value: String| {
    //                 connection.send(resp_array!["SET", "LASTCTR", value])
    //             })
    //     });
    //     let result: String = core.run(connect_f).unwrap();
    //     assert_eq!(result, "OK");
    // }

    // #[test]
    // fn sending_a_lot_of_data_test() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();

    //     let test_f = super::paired_connect(&addr);
    //     let send_data = test_f.and_then(|connection| {
    //         let mut futures = Vec::with_capacity(1000);
    //         for i in 0..1000 {
    //             let key = format!("X_{}", i);
    //             faf!(connection.send(resp_array!["SET", &key, i.to_string()]));
    //             futures.push(connection.send(resp_array!["GET", key]));
    //         }
    //         futures.remove(999)
    //     });
    //     let result: String = core.run(send_data).unwrap();
    //     assert_eq!(result, "999");
    // }

    // #[test]
    // fn pubsub_test() {
    //     let addr = "127.0.0.1:6379".parse().unwrap();
    //     let paired_c = super::paired_connect(&addr);
    //     let pubsub_c = super::pubsub_connect(&addr);
    //     let msgs = paired_c.join(pubsub_c).and_then(|(paired, pubsub)| {
    //         let subscribe = pubsub.subscribe("test-topic");
    //         subscribe.and_then(move |msgs| {
    //             faf!(paired.send(resp_array!["PUBLISH", "test-topic", "test-message"]));
    //             faf!(paired.send(resp_array!["PUBLISH", "test-not-topic", "test-message-1.5"]));
    //             paired
    //                 .send(resp_array!["PUBLISH", "test-topic", "test-message2"])
    //                 .map(|_: resp::RespValue| msgs)
    //         })
    //     });
    //     let tst = msgs.and_then(|msgs| {
    //         msgs.take(2)
    //             .collect()
    //             .map_err(|_| error::internal("unreachable"))
    //     });
    //     let result = core.run(tst).unwrap();
    //     assert_eq!(result.len(), 2);
    //     assert_eq!(result[0], "test-message".into());
    //     assert_eq!(result[1], "test-message2".into());
    // }
}
<|endoftext|>


<commit_before>use futures::{Async, Future, Poll, Sink};
use futures::stream::Stream;
use futures::sink::Send;
use futures_state_stream::{StateStream, StreamEvent};

use native_tls::TlsConnector;

use std::io;
use std::net::ToSocketAddrs;

use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::AsyncRead;
use tokio_core::reactor::Handle;
use tokio_tls::{ConnectAsync, TlsConnectorExt};

use imap_proto::{Request, RequestId, State};
use imap_proto::builders::command::Command;
use proto::{ImapCodec, ImapTransport, ResponseData};

pub mod builder {
    pub use imap_proto::builders::command::CommandBuilder;
}

pub struct Client {
    transport: ImapTransport,
    state: ClientState,
}

impl Client {
    pub fn connect(server: &str, handle: &Handle) -> ConnectFuture {
        let addr = format!("{}:993", server);
        let addr = addr.to_socket_addrs().unwrap().next().unwrap();
        let stream = TcpStream::connect(&addr, handle);
        ConnectFuture::TcpConnecting(stream, server.to_string())
    }

    pub fn call(self, cmd: Command) -> ResponseStream {
        let Self { transport, mut state } = self;
        let request_id = state.request_ids.next().unwrap();
        let (cmd_bytes, next_state) = cmd.into_parts();
        let future = transport.send(Request(request_id.clone(), cmd_bytes));
        ResponseStream::new(future, state, request_id, next_state)
    }
}

pub struct ResponseStream {
    future: Option<Send<ImapTransport>>,
    transport: Option<ImapTransport>,
    state: Option<ClientState>,
    request_id: RequestId,
    next_state: Option<State>,
    done: bool,
}

impl ResponseStream {
    pub fn new(future: Send<ImapTransport>, state: ClientState,
               request_id: RequestId, next_state: Option<State>) -> Self {
        Self {
            future: Some(future),
            transport: None,
            state: Some(state),
            request_id: request_id,
            next_state: next_state,
            done: false,
        }
    }
}

impl StateStream for ResponseStream {
    type Item = ResponseData;
    type State = Client;
    type Error = io::Error;
    fn poll(&mut self) -> Poll<StreamEvent<Self::Item, Self::State>, Self::Error> {
        if self.future.is_some() {
            let mut future = self.future.take().unwrap();
            match future.poll() {
                Ok(Async::Ready(transport)) => {
                    self.transport = Some(transport);
                },
                Ok(Async::NotReady) => {
                    self.future = Some(future);
                    return Ok(Async::NotReady);
                },
                Err(e) => {
                    return Err(e);
                },
            }
        }
        if !self.transport.is_some() {
            return Ok(Async::NotReady);
        }
        let mut transport = self.transport.take().unwrap();
        if self.done {
            let mut state = self.state.take().unwrap();
            if self.next_state.is_some() {
                state.state = self.next_state.take().unwrap();
            }
            let client = Client { transport, state };
            return Ok(Async::Ready(StreamEvent::Done(client)));
        }
        match transport.poll() {
            Ok(Async::Ready(Some(rsp))) => {
                if let Some(req_id) = rsp.request_id() {
                    self.done = *req_id == self.request_id;
                };
                self.transport = Some(transport);
                return Ok(Async::Ready(StreamEvent::Next(rsp)));
            },
            Err(e) => {
                return Err(e);
            },
            _ => (),
        }
        self.transport = Some(transport);
        Ok(Async::NotReady)
    }
}

pub enum ConnectFuture {
    #[doc(hidden)] TcpConnecting(TcpStreamNew, String),
    #[doc(hidden)] TlsHandshake(ConnectAsync<TcpStream>),
    #[doc(hidden)] ServerGreeting(Option<ImapTransport>),
}

impl Future for ConnectFuture {
    type Item = (Client, ResponseData);
    type Error = io::Error;
    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let mut new = None;
        if let ConnectFuture::TcpConnecting(ref mut future, ref domain) = *self {
            let stream = try_ready!(future.poll());
            let ctx = TlsConnector::builder().unwrap().build().unwrap();
            let future = ctx.connect_async(domain, stream);
            new = Some(ConnectFuture::TlsHandshake(future));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::TlsHandshake(ref mut future) = *self {
            let transport = try_ready!(future.map_err(|e| {
                io::Error::new(io::ErrorKind::Other, e)
            }).poll()).framed(ImapCodec::default());
            new = Some(ConnectFuture::ServerGreeting(Some(transport)));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::ServerGreeting(ref mut wrapped) = *self {
            let msg = try_ready!(wrapped.as_mut().unwrap().poll()).unwrap();
            return Ok(Async::Ready((Client {
                transport: wrapped.take().unwrap(),
                state: ClientState::new(),
            }, msg)));
        }
        Ok(Async::NotReady)
    }
}

pub struct ClientState {
    state: State,
    request_ids: IdGenerator,
}

impl ClientState {
    pub fn new() -> Self {
        Self {
            state: State::NotAuthenticated,
            request_ids: IdGenerator::new(),
        }
    }
}

impl Default for ClientState {
    fn default() -> Self {
        Self::new()
    }
}

pub struct IdGenerator {
    next: u64,
}

impl IdGenerator {
    pub fn new() -> Self {
        Self { next: 0 }
    }
}

impl Default for IdGenerator {
    fn default() -> Self {
        Self::new()
    }
}

impl Iterator for IdGenerator {
    type Item = RequestId;
    fn next(&mut self) -> Option<Self::Item> {
        self.next += 1;
        Some(RequestId(format!("A{:04}", self.next % 10_000)))
    }
}
<commit_msg>Export all command builder types<commit_after>use futures::{Async, Future, Poll, Sink};
use futures::stream::Stream;
use futures::sink::Send;
use futures_state_stream::{StateStream, StreamEvent};

use native_tls::TlsConnector;

use std::io;
use std::net::ToSocketAddrs;

use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::AsyncRead;
use tokio_core::reactor::Handle;
use tokio_tls::{ConnectAsync, TlsConnectorExt};

use imap_proto::{Request, RequestId, State};
use imap_proto::builders::command::Command;
use proto::{ImapCodec, ImapTransport, ResponseData};

pub mod builder {
    pub use imap_proto::builders::command::{CommandBuilder, FetchBuilderAttributes,
                                            FetchBuilderMessages, FetchBuilderModifiers,
                                            FetchCommand, FetchCommandAttributes,
                                            FetchCommandMessages};
}

pub struct Client {
    transport: ImapTransport,
    state: ClientState,
}

impl Client {
    pub fn connect(server: &str, handle: &Handle) -> ConnectFuture {
        let addr = format!("{}:993", server);
        let addr = addr.to_socket_addrs().unwrap().next().unwrap();
        let stream = TcpStream::connect(&addr, handle);
        ConnectFuture::TcpConnecting(stream, server.to_string())
    }

    pub fn call(self, cmd: Command) -> ResponseStream {
        let Self { transport, mut state } = self;
        let request_id = state.request_ids.next().unwrap();
        let (cmd_bytes, next_state) = cmd.into_parts();
        let future = transport.send(Request(request_id.clone(), cmd_bytes));
        ResponseStream::new(future, state, request_id, next_state)
    }
}

pub struct ResponseStream {
    future: Option<Send<ImapTransport>>,
    transport: Option<ImapTransport>,
    state: Option<ClientState>,
    request_id: RequestId,
    next_state: Option<State>,
    done: bool,
}

impl ResponseStream {
    pub fn new(future: Send<ImapTransport>, state: ClientState,
               request_id: RequestId, next_state: Option<State>) -> Self {
        Self {
            future: Some(future),
            transport: None,
            state: Some(state),
            request_id: request_id,
            next_state: next_state,
            done: false,
        }
    }
}

impl StateStream for ResponseStream {
    type Item = ResponseData;
    type State = Client;
    type Error = io::Error;
    fn poll(&mut self) -> Poll<StreamEvent<Self::Item, Self::State>, Self::Error> {
        if self.future.is_some() {
            let mut future = self.future.take().unwrap();
            match future.poll() {
                Ok(Async::Ready(transport)) => {
                    self.transport = Some(transport);
                },
                Ok(Async::NotReady) => {
                    self.future = Some(future);
                    return Ok(Async::NotReady);
                },
                Err(e) => {
                    return Err(e);
                },
            }
        }
        if !self.transport.is_some() {
            return Ok(Async::NotReady);
        }
        let mut transport = self.transport.take().unwrap();
        if self.done {
            let mut state = self.state.take().unwrap();
            if self.next_state.is_some() {
                state.state = self.next_state.take().unwrap();
            }
            let client = Client { transport, state };
            return Ok(Async::Ready(StreamEvent::Done(client)));
        }
        match transport.poll() {
            Ok(Async::Ready(Some(rsp))) => {
                if let Some(req_id) = rsp.request_id() {
                    self.done = *req_id == self.request_id;
                };
                self.transport = Some(transport);
                return Ok(Async::Ready(StreamEvent::Next(rsp)));
            },
            Err(e) => {
                return Err(e);
            },
            _ => (),
        }
        self.transport = Some(transport);
        Ok(Async::NotReady)
    }
}

pub enum ConnectFuture {
    #[doc(hidden)] TcpConnecting(TcpStreamNew, String),
    #[doc(hidden)] TlsHandshake(ConnectAsync<TcpStream>),
    #[doc(hidden)] ServerGreeting(Option<ImapTransport>),
}

impl Future for ConnectFuture {
    type Item = (Client, ResponseData);
    type Error = io::Error;
    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let mut new = None;
        if let ConnectFuture::TcpConnecting(ref mut future, ref domain) = *self {
            let stream = try_ready!(future.poll());
            let ctx = TlsConnector::builder().unwrap().build().unwrap();
            let future = ctx.connect_async(domain, stream);
            new = Some(ConnectFuture::TlsHandshake(future));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::TlsHandshake(ref mut future) = *self {
            let transport = try_ready!(future.map_err(|e| {
                io::Error::new(io::ErrorKind::Other, e)
            }).poll()).framed(ImapCodec::default());
            new = Some(ConnectFuture::ServerGreeting(Some(transport)));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::ServerGreeting(ref mut wrapped) = *self {
            let msg = try_ready!(wrapped.as_mut().unwrap().poll()).unwrap();
            return Ok(Async::Ready((Client {
                transport: wrapped.take().unwrap(),
                state: ClientState::new(),
            }, msg)));
        }
        Ok(Async::NotReady)
    }
}

pub struct ClientState {
    state: State,
    request_ids: IdGenerator,
}

impl ClientState {
    pub fn new() -> Self {
        Self {
            state: State::NotAuthenticated,
            request_ids: IdGenerator::new(),
        }
    }
}

impl Default for ClientState {
    fn default() -> Self {
        Self::new()
    }
}

pub struct IdGenerator {
    next: u64,
}

impl IdGenerator {
    pub fn new() -> Self {
        Self { next: 0 }
    }
}

impl Default for IdGenerator {
    fn default() -> Self {
        Self::new()
    }
}

impl Iterator for IdGenerator {
    type Item = RequestId;
    fn next(&mut self) -> Option<Self::Item> {
        self.next += 1;
        Some(RequestId(format!("A{:04}", self.next % 10_000)))
    }
}
<|endoftext|>


<commit_before>use std::string::ToString;
use std::path::PathBuf;
use std::io::{self, BufWriter};
use std::fs::File;
use std::collections::HashMap;

use futures_cpupool::CpuPool;

#[derive(Debug)]
pub enum Line {
    // PMs are logged sometimes trigger alerts (and are sent to client(s))
    PrivMsg { src: String, dst: String, text: String, orig: String },
    // Metadata is not logged (but is sent to client(s))
    Meta { orig: String },
    // Pings must be ponged but are neither logged nor sent to client(s)
    Ping { orig: String },
}

impl Line {
    pub fn new_meta(s: &str) -> Self {
        Line::Meta{ orig: s.to_string() }
    }
    pub fn new_ping(s: &str) -> Self {
        Line::Ping{ orig: s.to_string() }
    }
    pub fn new_pm(src: &str, dst: &str, text: &str, orig: &str) -> Self {
        Line::PrivMsg{
            src: src.to_string(),
            dst: dst.to_string(),
            text: text.to_string(),
            orig: orig.to_string(),
        }
    }
    pub fn pong_from_ping(p: &str) -> Line {
        let s = p.replacen("PING ", "PONG ", 1);
        Line::Ping { orig: s }
    }
    pub fn from_str(input: &str) -> Self {
        // TODO: adhere closer to the RFC
        // e.g. `:Angel!wings@irc.org PRIVMSG Wiz message goes here`
        // TODO: treat PRIVMSG and NOTICE differently?
        // TODO: handle '\r' better?
        let in_fixed = input.trim_right();
        let mut parts = in_fixed.splitn(4, ' ');
        let a = parts.nth(0);
        let b = parts.nth(0);
        let c = parts.nth(0);
        let d = parts.nth(0);
        match (a, b, c, d) {
            (Some(s), Some("PRIVMSG"), Some(d), Some(m)) |
                (Some(s), Some("NOTICE"), Some(d), Some(m)) =>
            {
                let i = if s.starts_with(':') { 1 } else { 0 };
                let j = s.find('!').unwrap_or(s.len()-1);
                let src_fixed = &s[i..j];
                let msg_fixed = if m.starts_with(':') { &m[1..] } else { m };
                Line::new_pm(src_fixed, d, msg_fixed, in_fixed)
            },
            (Some("PING"), _, _, _) => Line::new_ping(in_fixed),
            _ => Line::new_meta(input)
        }
    }
}

impl ToString for Line {
    fn to_string(&self) -> String {
        match *self {
            Line::PrivMsg { orig: ref o, .. } => o,
            Line::Meta { orig: ref o, .. } => o,
            Line::Ping { orig: ref o, .. } => o,
        }.clone()
    }
}

use tokio_timer::{Timer, Timeout, TimerError};
use futures_cpupool::CpuFuture;
use futures::Future;
use std::io::Write;
use std::thread;
use std::time::Duration;
use std::marker::Send;
pub struct FileClosed;
const FILE_TIMEOUT_MS: u64 = 1_000;

// Maintain on-disk logs *in addition to* in-memory buffer
// TODO: don't use a box
pub enum FileState {
    Open {
        writer: BufWriter<File>,
        timeout: Box<Future<Item=FileClosed, Error=TimerError> + Send>,
        //timer: Option<Timeout<CpuFuture<FileClosed, ()>>>,
        path: PathBuf,
    },
    Closed(PathBuf),
}

impl FileState {
    fn is_open(&self) -> bool {
        if let &FileState::Open{..} = self {
            true
        } else {
            false
        }
    }
    fn clone_path(&self) -> PathBuf {
        match self {
            &FileState::Open { path: ref p, .. } => p,
            &FileState::Closed(ref p) => p,
        }.clone()
    }
    fn open(self) -> (io::Result<FileState>,
                      Option<Box<Future<Item=FileState, Error=TimerError>>>)
    {
        if self.is_open() {
            (Ok(self),None)
        } else {
            let pb = self.clone_path();
            let timer = Timer::default();
            let close_event = timer.sleep(Duration::from_millis(FILE_TIMEOUT_MS))
                .then(|t| t.map(|_| self.close()));
            //FileState::
            //let close: CpuFuture<FileState,io::Error> = pool.spawn_fn(|| {
            //    thread::sleep(Duration::from_millis(FILE_TIMEOUT_MS));
            //    self.close()
            //});
            //let todo = Duration::from_millis(2*FILE_TIMEOUT_MS);
            //let timeout = timer.timeout(close, todo);
            unimplemented!()
        }
    }
    fn close(self) -> io::Result<FileState> {
        //thread::sleep(Duration::from_millis(FILE_TIMEOUT_MS));
        if let FileState::Open{ writer: mut w, path: pb, .. } = self {
            w.flush()?;
            Ok(FileState::Closed(pb))
        } else {
            Ok(self)
        }
    }
}

pub struct Logs(HashMap<String,FileState>);
/*
pub struct Logs {
    files: HashMap<String,FileState>,
    pool: CpuPool,
//(HashMap<String,FileState>);
}

impl Logs {
    pub fn new() -> Self {
        Logs {
            files: HashMap::new(),
            pool: CpuPool::new(2),
        }
    }

}
*/
/*
impl Logs {
    pub fn new() -> Self {
        Logs {
            files: HashMap::new(),
            pool: CpuPool::new(2),
        }
    }
    pub fn add(&mut self, name: &str) {
        if self.files.contains_key(name) == false {
            let val = FileState::Closed(PathBuf::from(name));
            self.files.insert(name.to_string(), val);
        } else {
            panic!("Value duplicated");
        }
    }
    pub fn open(&mut self, name: &str) -> io::Result<&BufWriter<File>> {
        let mut file = File::open(name)?;
        let val = FileState::Open(BufWriter::new(file));
        self.files.insert(name.to_owned(), val);
        match self.files.get(name) {
            Some(&FileState::Open(ref br)) => Ok(br),
            _ => unreachable!(),
        }
    }
    pub fn write_and_close(&mut self, name: &str) -> io::Result<()> {
        let g = self.files.get(name);
        let w = match g {
            Some(&FileState::Open(ref b)) => b,
            Some(&FileState::Closed(_)) => {
                self.open(name)?
            },
            None => {
                self.add(name);
                self.open(name)?
            }
        };
        Ok(())
    }
    //pub fn new() -> Self {
    //    Logs(HashMap::new())
    //}
    //pub fn update(line: &Line) -> Result<usize,io::Error> {
    //    Ok(0)
    //}
}
*/

<commit_msg>Got slightly closer, but still far from elegant/done<commit_after>use std::string::ToString;
use std::path::PathBuf;
use std::io::{self, BufWriter};
use std::fs::File;
use std::collections::HashMap;

use futures_cpupool::CpuPool;

#[derive(Debug)]
pub enum Line {
    // PMs are logged sometimes trigger alerts (and are sent to client(s))
    PrivMsg { src: String, dst: String, text: String, orig: String },
    // Metadata is not logged (but is sent to client(s))
    Meta { orig: String },
    // Pings must be ponged but are neither logged nor sent to client(s)
    Ping { orig: String },
}

impl Line {
    pub fn new_meta(s: &str) -> Self {
        Line::Meta{ orig: s.to_string() }
    }
    pub fn new_ping(s: &str) -> Self {
        Line::Ping{ orig: s.to_string() }
    }
    pub fn new_pm(src: &str, dst: &str, text: &str, orig: &str) -> Self {
        Line::PrivMsg{
            src: src.to_string(),
            dst: dst.to_string(),
            text: text.to_string(),
            orig: orig.to_string(),
        }
    }
    pub fn pong_from_ping(p: &str) -> Line {
        let s = p.replacen("PING ", "PONG ", 1);
        Line::Ping { orig: s }
    }
    pub fn from_str(input: &str) -> Self {
        // TODO: adhere closer to the RFC
        // e.g. `:Angel!wings@irc.org PRIVMSG Wiz message goes here`
        // TODO: treat PRIVMSG and NOTICE differently?
        // TODO: handle '\r' better?
        let in_fixed = input.trim_right();
        let mut parts = in_fixed.splitn(4, ' ');
        let a = parts.nth(0);
        let b = parts.nth(0);
        let c = parts.nth(0);
        let d = parts.nth(0);
        match (a, b, c, d) {
            (Some(s), Some("PRIVMSG"), Some(d), Some(m)) |
                (Some(s), Some("NOTICE"), Some(d), Some(m)) =>
            {
                let i = if s.starts_with(':') { 1 } else { 0 };
                let j = s.find('!').unwrap_or(s.len()-1);
                let src_fixed = &s[i..j];
                let msg_fixed = if m.starts_with(':') { &m[1..] } else { m };
                Line::new_pm(src_fixed, d, msg_fixed, in_fixed)
            },
            (Some("PING"), _, _, _) => Line::new_ping(in_fixed),
            _ => Line::new_meta(input)
        }
    }
}

impl ToString for Line {
    fn to_string(&self) -> String {
        match *self {
            Line::PrivMsg { orig: ref o, .. } => o,
            Line::Meta { orig: ref o, .. } => o,
            Line::Ping { orig: ref o, .. } => o,
        }.clone()
    }
}

use tokio_timer::{Timer, Timeout, TimerError};
use futures_cpupool::CpuFuture;
use futures::Future;
use std::io::Write;
use std::thread;
use std::time::Duration;
use std::marker::Send;
pub struct FileClosed;
const FILE_TIMEOUT_MS: u64 = 1_000;

// Maintain on-disk logs *in addition to* in-memory buffer
// TODO: don't use a box
pub enum FileState {
    Open {
        writer: BufWriter<File>,
        //timeout: Box<Future<Item=io::Result<FileState>, Error=TimerError> + Send>,
        timeout: Box<Future<Item=FileState, Error=TimerError> + Send>,
        // can we chain onto this afterwards? p sure
        //timer: Option<Timeout<CpuFuture<FileClosed, ()>>>,
        path: PathBuf,
    },
    Closed(PathBuf),
}

impl FileState {
    fn new(name: &str) -> Self {
        FileState::Closed(PathBuf::from(name))
    }
    fn is_open(&self) -> bool {
        if let &FileState::Open{..} = self { true } else { false }
    }
    fn clone_path(&self) -> PathBuf {
        match self {
            &FileState::Open { path: ref p, .. } => p,
            &FileState::Closed(ref p) => p,
        }.clone()
    }
    fn open(self) -> io::Result<FileState> {
        if self.is_open() {
            Ok(self)
        } else {
            let pb = self.clone_path();
            let mut f = File::open(&pb)?;
            let bw = BufWriter::new(f);
            let timer = Timer::default();
            let close_event = timer.sleep(Duration::from_millis(FILE_TIMEOUT_MS))
                .then(|t| t.map(|_| self.close().unwrap()));
            Ok(FileState::Open {
                writer:  bw,
                timeout: close_event.boxed(),
                path:    pb,
            })
        }
    }
    fn close(self) -> io::Result<FileState> {
        if let FileState::Open{ writer: mut w, path: pb, .. } = self {
            w.flush()?;
            Ok(FileState::Closed(pb))
        } else {
            Ok(self)
        }
    }
}

pub struct Logs(HashMap<String,FileState>);
use futures;

impl Logs {
    pub fn new() -> Self {
        Logs(HashMap::new())
    }
    pub fn add(&mut self, name: &str) {
        // TODO: not clobber?
        let val = FileState::new(name);
        self.0.insert(name.to_string(), val);
    }
    pub fn open(&mut self, name: String) {
        let old_val = self.0.remove(&name).unwrap_or(FileState::new(&name));
        let new_val = if old_val.is_open() {
            let tmp = old_val.open().unwrap();
            if let FileState::Open{ timeout: ref t, .. } = tmp {
                t.and_then(|x| futures::future::ok(()));

            }
            tmp
        } else {
            old_val
            //self.0.insert(name.to_string(), old_val);
        };
        self.0.insert(name, new_val);
        //let entry = self.0.entry(name.to_string())
        //    .or_insert(FileState::new(name));
        //if entry.is_open() == false {
        //    let val = entry.open();
        //}
    }

}
/*
pub struct Logs {
    files: HashMap<String,FileState>,
    pool: CpuPool,
//(HashMap<String,FileState>);
}

impl Logs {
    pub fn new() -> Self {
        Logs {
            files: HashMap::new(),
            pool: CpuPool::new(2),
        }
    }

}
*/
/*
impl Logs {
    pub fn new() -> Self {
        Logs {
            files: HashMap::new(),
            pool: CpuPool::new(2),
        }
    }
    pub fn add(&mut self, name: &str) {
        if self.files.contains_key(name) == false {
            let val = FileState::Closed(PathBuf::from(name));
            self.files.insert(name.to_string(), val);
        } else {
            panic!("Value duplicated");
        }
    }
    pub fn open(&mut self, name: &str) -> io::Result<&BufWriter<File>> {
        let mut file = File::open(name)?;
        let val = FileState::Open(BufWriter::new(file));
        self.files.insert(name.to_owned(), val);
        match self.files.get(name) {
            Some(&FileState::Open(ref br)) => Ok(br),
            _ => unreachable!(),
        }
    }
    pub fn write_and_close(&mut self, name: &str) -> io::Result<()> {
        let g = self.files.get(name);
        let w = match g {
            Some(&FileState::Open(ref b)) => b,
            Some(&FileState::Closed(_)) => {
                self.open(name)?
            },
            None => {
                self.add(name);
                self.open(name)?
            }
        };
        Ok(())
    }
    //pub fn new() -> Self {
    //    Logs(HashMap::new())
    //}
    //pub fn update(line: &Line) -> Result<usize,io::Error> {
    //    Ok(0)
    //}
}
*/

<|endoftext|>


<commit_before>extern crate yaml_rust;

#[macro_use] mod generation;

use chrono::{Local, Datelike};
use feed::Feed;
use self::yaml_rust::{YamlLoader, Yaml};
use std::path::Path;

error_chain! {
    links {
        Util(::util::Error, ::util::ErrorKind);
    }

    foreign_links {
        Yaml(yaml_rust::ScanError);
    }
}

create_config_struct!(Spike,
    jump:                    f32 => "Jump Required"                        => 0.25,
    low_listener_increase:   f32 => "Low Listener Increase"                => [0.0, 0.005],
    high_listener_dec:       f32 => "High Listener Decrease"               => [0.0, 0.02],
    high_listener_dec_every: f32 => "High Listener Decrease Per Listeners" => [1.0, 100.0],
);

create_config_struct!(UnskewedAverage,
    reset_pcnt:      f32 => "Reset To Average Percentage"  => [0.0, 0.15],
    adjust_pcnt:     f32 => "Adjust to Average Percentage" => [0.0, 0.0075],
    spikes_required: u8  => "Listener Spikes Required"     => 1,
    jump_required:   f32 => "Listener Jump Required"       => [1.1, 4.0],
);

create_config_enum!(FeedIdent,
    Name(String)   => self,
    ID(u32)        => self,
    County(String) => self,
    State(u32)     => "State ID",
);

impl FeedIdent {
    pub fn matches_feed(&self, feed: &Feed) -> bool {
        match *self {
            FeedIdent::Name(ref name) => *name == feed.name,
            FeedIdent::ID(id)         => id == feed.id,
            FeedIdent::County(ref c)  => *c == feed.county,
            FeedIdent::State(id)      => id == feed.state_id,
        }
    }
}

create_config_enum!(WeekdaySpike,
    Sunday(Spike)    => self,
    Monday(Spike)    => self,
    Tuesday(Spike)   => self,
    Wednesday(Spike) => self,
    Thursday(Spike)  => self,
    Friday(Spike)    => self,
    Saturday(Spike)  => self,
);

impl WeekdaySpike {
    pub fn get_for_today(weekday_spikes: &[WeekdaySpike]) -> Option<&Spike> {
        use chrono::Weekday::*;
        use self::WeekdaySpike::*;

        let weekday = Local::today().weekday();

        for ws in weekday_spikes {
            match (weekday, ws) {
                (Mon, &Monday(ref s))    |
                (Tue, &Tuesday(ref s))   |
                (Wed, &Wednesday(ref s)) |
                (Thu, &Thursday(ref s))  |
                (Fri, &Friday(ref s))    |
                (Sat, &Saturday(ref s))  |
                (Sun, &Sunday(ref s)) => return Some(&s),
                _ => (),
            }
        }

        None
    }
}

create_config_struct!(FeedSetting,
    ident:          FeedIdent         => self                        => fail,
    spike:          Spike             => "Spike Percentages"         => default,
    weekday_spikes: Vec<WeekdaySpike> => "Weekday Spike Percentages" => all,
);

create_config_struct!(Misc,
	update_time:       f32         => "Update Time"       => [5.0, 6.0],
	minimum_listeners: u32         => "Minimum Listeners" => 15,
	state_feeds_id:    Option<u32> => "State Feeds ID"    => None,
    sort_order:        SortOrder   => "Feed Sort Order"   => (SortOrder::Descending),
);

create_config_enum!(SortOrder,
    Ascending  => self,
    Descending => self,
);

macro_rules! gen_base_parse_stmt {
    (optional, $category:expr, $doc:ident) => (ParseYaml::from(&$doc[$category]));
    (default,  $category:expr, $doc:ident) => (ParseYaml::from_or_default(&$doc[$category]));
    (all,      $category:expr, $doc:ident) => (ParseYaml::all(&$doc[$category]));
}

macro_rules! gen_base_config {
    ($name:ident, $($field:ident: $type:ty => $parse_type:ident => $category:expr,)+) => {
        #[derive(Debug, Default)]
        pub struct $name {
            $(pub $field: $type,)+
        }

        impl $name {
            pub fn from_file(path: &Path) -> Result<$name> {
                let file = ::util::read_file(path)?;

                if file.len() == 0 {
                    return Ok(Config::default())
                }

                let doc = YamlLoader::load_from_str(&::util::read_file(path)?)?;
                let doc = &doc[0]; // We only care about the first document

                Ok($name {
                    $($field: gen_base_parse_stmt!($parse_type, $category, doc),)+
                })
            }
        }
    };
}

gen_base_config!(Config,
    global_spike:   Spike             => default => "Spike Percentage",
    unskewed_avg:   UnskewedAverage   => default => "Unskewed Average",
    weekday_spikes: Vec<WeekdaySpike> => all     => "Weekday Spike Percentages",
    feed_settings:  Vec<FeedSetting>  => all     => "Feed Settings",
    misc:           Misc              => default => "Misc",
    blacklist:      Vec<FeedIdent>    => all     => "Blacklist",
    whitelist:      Vec<FeedIdent>    => all     => "Whitelist",
);

impl Config {
    pub fn get_feed_spike(&self, feed: &Feed) -> &Spike {
        // Find any settings for the specified feed
        let feed_setting = self.feed_settings
            .iter()
            .find(|s| s.ident.matches_feed(&feed));

        match feed_setting {
            Some(setting) => {
                WeekdaySpike::get_for_today(&setting.weekday_spikes)
                    .unwrap_or(&setting.spike)
            },
            None => {
                WeekdaySpike::get_for_today(&self.weekday_spikes)
                    .unwrap_or(&self.global_spike)
            }
        }
    }
}

trait ParseYaml: Sized + Default {
    fn from(doc: &Yaml) -> Option<Self>;

    fn from_or_default(doc: &Yaml) -> Self {
        ParseYaml::from(&doc).unwrap_or(Self::default())
    }

    fn all(doc: &Yaml) -> Vec<Self> {
        doc.as_vec()
           .unwrap_or(&Vec::new())
           .iter()
           .filter_map(ParseYaml::from)
           .collect()
    }
}

macro_rules! impl_parseyaml_for_numeric {
    ($($t:ty )+) => {
        $(
        impl ParseYaml for $t {
            fn from(doc: &Yaml) -> Option<$t> {
                use self::yaml_rust::Yaml::*;
                match *doc {
                    Integer(num)     => Some(num as $t),
                    Real(ref string) => string.parse().ok(),
                    _                => None,
                }
            }
        }
        )+
    }
}

impl_parseyaml_for_numeric!(u8 u32 f32);

impl ParseYaml for String {
    fn from(doc: &Yaml) -> Option<String> {
        use self::yaml_rust::Yaml::*;
        match *doc {
            String(ref s) => Some(s.clone()),
            _             => None,
        }
    }
}<commit_msg>Increase default spike percentage required<commit_after>extern crate yaml_rust;

#[macro_use] mod generation;

use chrono::{Local, Datelike};
use feed::Feed;
use self::yaml_rust::{YamlLoader, Yaml};
use std::path::Path;

error_chain! {
    links {
        Util(::util::Error, ::util::ErrorKind);
    }

    foreign_links {
        Yaml(yaml_rust::ScanError);
    }
}

create_config_struct!(Spike,
    jump:                    f32 => "Jump Required"                        => 0.3,
    low_listener_increase:   f32 => "Low Listener Increase"                => [0.0, 0.005],
    high_listener_dec:       f32 => "High Listener Decrease"               => [0.0, 0.02],
    high_listener_dec_every: f32 => "High Listener Decrease Per Listeners" => [1.0, 100.0],
);

create_config_struct!(UnskewedAverage,
    reset_pcnt:      f32 => "Reset To Average Percentage"  => [0.0, 0.15],
    adjust_pcnt:     f32 => "Adjust to Average Percentage" => [0.0, 0.0075],
    spikes_required: u8  => "Listener Spikes Required"     => 1,
    jump_required:   f32 => "Listener Jump Required"       => [1.1, 4.0],
);

create_config_enum!(FeedIdent,
    Name(String)   => self,
    ID(u32)        => self,
    County(String) => self,
    State(u32)     => "State ID",
);

impl FeedIdent {
    pub fn matches_feed(&self, feed: &Feed) -> bool {
        match *self {
            FeedIdent::Name(ref name) => *name == feed.name,
            FeedIdent::ID(id)         => id == feed.id,
            FeedIdent::County(ref c)  => *c == feed.county,
            FeedIdent::State(id)      => id == feed.state_id,
        }
    }
}

create_config_enum!(WeekdaySpike,
    Sunday(Spike)    => self,
    Monday(Spike)    => self,
    Tuesday(Spike)   => self,
    Wednesday(Spike) => self,
    Thursday(Spike)  => self,
    Friday(Spike)    => self,
    Saturday(Spike)  => self,
);

impl WeekdaySpike {
    pub fn get_for_today(weekday_spikes: &[WeekdaySpike]) -> Option<&Spike> {
        use chrono::Weekday::*;
        use self::WeekdaySpike::*;

        let weekday = Local::today().weekday();

        for ws in weekday_spikes {
            match (weekday, ws) {
                (Mon, &Monday(ref s))    |
                (Tue, &Tuesday(ref s))   |
                (Wed, &Wednesday(ref s)) |
                (Thu, &Thursday(ref s))  |
                (Fri, &Friday(ref s))    |
                (Sat, &Saturday(ref s))  |
                (Sun, &Sunday(ref s)) => return Some(&s),
                _ => (),
            }
        }

        None
    }
}

create_config_struct!(FeedSetting,
    ident:          FeedIdent         => self                        => fail,
    spike:          Spike             => "Spike Percentages"         => default,
    weekday_spikes: Vec<WeekdaySpike> => "Weekday Spike Percentages" => all,
);

create_config_struct!(Misc,
	update_time:       f32         => "Update Time"       => [5.0, 6.0],
	minimum_listeners: u32         => "Minimum Listeners" => 15,
	state_feeds_id:    Option<u32> => "State Feeds ID"    => None,
    sort_order:        SortOrder   => "Feed Sort Order"   => (SortOrder::Descending),
);

create_config_enum!(SortOrder,
    Ascending  => self,
    Descending => self,
);

macro_rules! gen_base_parse_stmt {
    (optional, $category:expr, $doc:ident) => (ParseYaml::from(&$doc[$category]));
    (default,  $category:expr, $doc:ident) => (ParseYaml::from_or_default(&$doc[$category]));
    (all,      $category:expr, $doc:ident) => (ParseYaml::all(&$doc[$category]));
}

macro_rules! gen_base_config {
    ($name:ident, $($field:ident: $type:ty => $parse_type:ident => $category:expr,)+) => {
        #[derive(Debug, Default)]
        pub struct $name {
            $(pub $field: $type,)+
        }

        impl $name {
            pub fn from_file(path: &Path) -> Result<$name> {
                let file = ::util::read_file(path)?;

                if file.len() == 0 {
                    return Ok(Config::default())
                }

                let doc = YamlLoader::load_from_str(&::util::read_file(path)?)?;
                let doc = &doc[0]; // We only care about the first document

                Ok($name {
                    $($field: gen_base_parse_stmt!($parse_type, $category, doc),)+
                })
            }
        }
    };
}

gen_base_config!(Config,
    global_spike:   Spike             => default => "Spike Percentage",
    unskewed_avg:   UnskewedAverage   => default => "Unskewed Average",
    weekday_spikes: Vec<WeekdaySpike> => all     => "Weekday Spike Percentages",
    feed_settings:  Vec<FeedSetting>  => all     => "Feed Settings",
    misc:           Misc              => default => "Misc",
    blacklist:      Vec<FeedIdent>    => all     => "Blacklist",
    whitelist:      Vec<FeedIdent>    => all     => "Whitelist",
);

impl Config {
    pub fn get_feed_spike(&self, feed: &Feed) -> &Spike {
        // Find any settings for the specified feed
        let feed_setting = self.feed_settings
            .iter()
            .find(|s| s.ident.matches_feed(&feed));

        match feed_setting {
            Some(setting) => {
                WeekdaySpike::get_for_today(&setting.weekday_spikes)
                    .unwrap_or(&setting.spike)
            },
            None => {
                WeekdaySpike::get_for_today(&self.weekday_spikes)
                    .unwrap_or(&self.global_spike)
            }
        }
    }
}

trait ParseYaml: Sized + Default {
    fn from(doc: &Yaml) -> Option<Self>;

    fn from_or_default(doc: &Yaml) -> Self {
        ParseYaml::from(&doc).unwrap_or(Self::default())
    }

    fn all(doc: &Yaml) -> Vec<Self> {
        doc.as_vec()
           .unwrap_or(&Vec::new())
           .iter()
           .filter_map(ParseYaml::from)
           .collect()
    }
}

macro_rules! impl_parseyaml_for_numeric {
    ($($t:ty )+) => {
        $(
        impl ParseYaml for $t {
            fn from(doc: &Yaml) -> Option<$t> {
                use self::yaml_rust::Yaml::*;
                match *doc {
                    Integer(num)     => Some(num as $t),
                    Real(ref string) => string.parse().ok(),
                    _                => None,
                }
            }
        }
        )+
    }
}

impl_parseyaml_for_numeric!(u8 u32 f32);

impl ParseYaml for String {
    fn from(doc: &Yaml) -> Option<String> {
        use self::yaml_rust::Yaml::*;
        match *doc {
            String(ref s) => Some(s.clone()),
            _             => None,
        }
    }
}<|endoftext|>