<commit_before>use animation::{AnimationState, AnimationSet};
use cell_state::CellState;
use game_data::GameData;
use game_scene::GameEvent;
use map::{CELL_SIZE, Adjacents};
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::Canvas;
use sdl2::video::Window;
use sdl2_engine_helpers::event_bus::EventBus;
use std::cell::RefCell;
use std::rc::Rc;
use transform::TransformContext;

pub trait EntityType {
    fn input(&mut self, state: &mut EntityState, key: Movement, adjacents: &Adjacents) {
    }
    fn think(&mut self, state: &mut EntityState, event_bus: &mut EventBus<GameEvent>, adjacents: &Adjacents, engine_data: &GameData, tick: u64) {
    }
    fn collisions(&self, state: &EntityState, event_bus: &mut EventBus<GameEvent>, cell_state: (CellState, Vec<RcEntity>)) {
    }
    fn is_hard(&self) -> bool {
        false
    }
    fn push(&mut self, direction: Movement, tick: u64) {
    }
    fn is_enterable(&self) -> bool {
        false
    }
    fn is_collectible(&self) -> bool {
        false
    }
    fn is_player(&self) -> bool {
        false
    }
    fn score(&self) -> u32 {
        0
    }
    fn open_exit(&mut self, state: &mut EntityState) {
    }
    fn destructable(&self) -> bool {
        true
    }
    fn render(&self, renderer: &mut Canvas<Window>, transform: &TransformContext, state: &EntityState, engine_data: &GameData, tick: u64) {
        renderer.set_draw_color(Color::RGB(255, 0, 0));
        transform.fill_rect(
            renderer,
            Rect::new(
                0,
                0,
                CELL_SIZE,
                CELL_SIZE
            )
        ).expect("Failed to draw entity");
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Movement {
    NEUTRAL,
    LEFT,
    RIGHT,
    UP,
    DOWN
}

#[derive(Eq, PartialEq)]
pub enum CellMoveState {
    NEUTRAL,
    ENTERING,
    EXITING
}

pub struct EntityState {
    pub id: u32,
    pub x: u32,
    pub y: u32,
    pub destroyed: bool,
    pub pos_fraction: f32,
    pub movement: Movement,
    pub cell_move_state: CellMoveState,
    pub animation_state: AnimationState,
}

impl EntityState {
    pub fn set_movement(&mut self, movement: Movement) {
        self.movement = movement;
        self.cell_move_state = CellMoveState::EXITING;
    }
}

pub struct Entity {
    pub state: EntityState,
    entity_type: Box<EntityType>
}

impl Entity {

    pub fn new(x: u32, y: u32, entity_type: Box<EntityType>, id: &mut u32) -> RcEntity {
        let item = Rc::new(RefCell::new(Entity {
            state: EntityState {
                x: x,
                y: y,
                pos_fraction: 0.0,
                movement: Movement::NEUTRAL,
                cell_move_state: CellMoveState::NEUTRAL,
                destroyed: false,
                animation_state: AnimationState::new(),
                id: *id
            },
            entity_type: entity_type
        }));
        *id += 1;
        item
    }

    pub fn render(&self, renderer: &mut Canvas<Window>, transform: &TransformContext, engine_data: &GameData, tick: u64) {
        self.entity_type.render(renderer, transform, &self.state, engine_data, tick);
    }

    pub fn think(&mut self, event_bus: &mut EventBus<GameEvent>, adjacents: &Adjacents, engine_data: &GameData, tick: u64) {
        self.entity_type.think(&mut self.state, event_bus, adjacents, engine_data, tick);
    }

    pub fn process(&mut self, tick: u64) {
        // TODO: This should be an event
        match self.state.cell_move_state {
            CellMoveState::NEUTRAL => (),
            CellMoveState::EXITING => {
                if self.state.pos_fraction >= 0.5 {
                    match self.state.movement {
                        Movement::LEFT => { self.state.x -= 1},
                        Movement::RIGHT => { self.state.x += 1},
                        Movement::UP => { self.state.y -= 1},
                        Movement::DOWN => { self.state.y += 1},
                        _ => {}
                    };
                    self.state.pos_fraction = -0.4;
                    self.state.cell_move_state = CellMoveState::ENTERING;
                } else {
                    self.state.pos_fraction += 0.1;
                }
            },
            CellMoveState::ENTERING => {
                if self.state.pos_fraction >= 0.0 {
                    self.state.pos_fraction = 0.0;
                    self.state.cell_move_state = CellMoveState::NEUTRAL;
                    self.state.movement = Movement::NEUTRAL;
                } else {
                    self.state.pos_fraction += 0.1;
                }
            }
        }

    }

    pub fn collisions(&self, event_bus: &mut EventBus<GameEvent>, cell_state: (CellState, Vec<RcEntity>)) {
        self.entity_type.collisions(&self.state, event_bus, cell_state)
    }

    pub fn input(&mut self, key: Movement, adjacents: &Adjacents) {
        self.entity_type.input(&mut self.state, key, adjacents);
    }

    pub fn get_abs_position(&self) -> (i32, i32) {
        let (x_offset, y_offset) = match self.state.movement {
            Movement::NEUTRAL => (0.0, 0.0),
            Movement::LEFT => (-self.state.pos_fraction, 0.0),
            Movement::RIGHT => (self.state.pos_fraction, 0.0),
            Movement::UP => (0.0, -self.state.pos_fraction),
            Movement::DOWN => (0.0, self.state.pos_fraction),
        };
        (
            ((self.state.x * CELL_SIZE) as i32 + (CELL_SIZE as f32 * x_offset) as i32) as i32,
            ((self.state.y * CELL_SIZE) as i32 + (CELL_SIZE as f32 * y_offset) as i32) as i32,
        )
    }

    pub fn is_hard(&self) -> bool {
        self.entity_type.is_hard()
    }

    pub fn target_square(&self) -> Option<(u32, u32)> {
        match self.state.cell_move_state {
            CellMoveState::EXITING => {
                Some(match self.state.movement {
                    Movement::NEUTRAL => (self.state.x, self.state.y),
                    Movement::LEFT => (self.state.x - 1, self.state.y),
                    Movement::RIGHT => (self.state.x + 1, self.state.y),
                    Movement::UP => (self.state.x, self.state.y - 1),
                    Movement::DOWN => (self.state.x, self.state.y + 1),
                })
            },
            _ => None
        }
    }

    pub fn deflect(&mut self) {
        match self.state.cell_move_state {
            CellMoveState::EXITING => {
                self.state.cell_move_state = CellMoveState::ENTERING;
                self.state.pos_fraction = 1.0 - self.state.pos_fraction;
            },
            _ => ()
        }
    }

    pub fn destroy(&mut self) {
        self.state.destroyed = true;
    }

    pub fn push(&mut self, direction: Movement, tick: u64) {
        self.entity_type.push(direction, tick);
    }

    pub fn is_enterable(&self) -> bool {
        self.entity_type.is_enterable()
    }

    pub fn is_collectible(&self) -> bool {
        self.entity_type.is_collectible()
    }

    pub fn is_player(&self) -> bool {
        self.entity_type.is_player()
    }

    pub fn score(&self) -> u32 {
        self.entity_type.score()
    }

    pub fn open_exit(&mut self) {
        self.entity_type.open_exit(&mut self.state)
    }

    pub fn destructable(&self) -> bool {
        self.entity_type.destructable()
    }
}

pub type RcEntity = Rc<RefCell<Entity>>;
<commit_msg>Better cell interpolation<commit_after>use animation::{AnimationState, AnimationSet};
use cell_state::CellState;
use game_data::GameData;
use game_scene::GameEvent;
use map::{CELL_SIZE, Adjacents};
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::Canvas;
use sdl2::video::Window;
use sdl2_engine_helpers::event_bus::EventBus;
use std::cell::RefCell;
use std::rc::Rc;
use transform::TransformContext;

fn partial_min(a: f64, b: f64) -> f64 {
    if (a < b) {
        a
    } else {
        b
    }
}

pub trait EntityType {
    fn input(&mut self, state: &mut EntityState, key: Movement, adjacents: &Adjacents) {
    }
    fn think(&mut self, state: &mut EntityState, event_bus: &mut EventBus<GameEvent>, adjacents: &Adjacents, engine_data: &GameData, tick: u64) {
    }
    fn collisions(&self, state: &EntityState, event_bus: &mut EventBus<GameEvent>, cell_state: (CellState, Vec<RcEntity>)) {
    }
    fn is_hard(&self) -> bool {
        false
    }
    fn push(&mut self, direction: Movement, tick: u64) {
    }
    fn is_enterable(&self) -> bool {
        false
    }
    fn is_collectible(&self) -> bool {
        false
    }
    fn is_player(&self) -> bool {
        false
    }
    fn score(&self) -> u32 {
        0
    }
    fn open_exit(&mut self, state: &mut EntityState) {
    }
    fn destructable(&self) -> bool {
        true
    }
    fn render(&self, renderer: &mut Canvas<Window>, transform: &TransformContext, state: &EntityState, engine_data: &GameData, tick: u64) {
        renderer.set_draw_color(Color::RGB(255, 0, 0));
        transform.fill_rect(
            renderer,
            Rect::new(
                0,
                0,
                CELL_SIZE,
                CELL_SIZE
            )
        ).expect("Failed to draw entity");
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Movement {
    NEUTRAL,
    LEFT,
    RIGHT,
    UP,
    DOWN
}

#[derive(Eq, PartialEq)]
pub enum CellMoveState {
    NEUTRAL,
    ENTERING,
    EXITING
}

pub struct EntityState {
    pub id: u32,
    pub x: u32,
    pub y: u32,
    pub destroyed: bool,
    pub pos_fraction: f64,
    pub movement: Movement,
    pub cell_move_state: CellMoveState,
    pub animation_state: AnimationState,
}

impl EntityState {
    pub fn set_movement(&mut self, movement: Movement) {
        self.movement = movement;
        self.cell_move_state = CellMoveState::EXITING;
    }
}

pub struct Entity {
    pub state: EntityState,
    entity_type: Box<EntityType>
}

impl Entity {

    pub fn new(x: u32, y: u32, entity_type: Box<EntityType>, id: &mut u32) -> RcEntity {
        let item = Rc::new(RefCell::new(Entity {
            state: EntityState {
                x: x,
                y: y,
                pos_fraction: 0.0,
                movement: Movement::NEUTRAL,
                cell_move_state: CellMoveState::NEUTRAL,
                destroyed: false,
                animation_state: AnimationState::new(),
                id: *id
            },
            entity_type: entity_type
        }));
        *id += 1;
        item
    }

    pub fn render(&self, renderer: &mut Canvas<Window>, transform: &TransformContext, engine_data: &GameData, tick: u64) {
        self.entity_type.render(renderer, transform, &self.state, engine_data, tick);
    }

    pub fn think(&mut self, event_bus: &mut EventBus<GameEvent>, adjacents: &Adjacents, engine_data: &GameData, tick: u64) {
        self.entity_type.think(&mut self.state, event_bus, adjacents, engine_data, tick);
    }

    pub fn process(&mut self, tick: u64) {
        // TODO: This should be an event
        match self.state.cell_move_state {
            CellMoveState::NEUTRAL => (),
            CellMoveState::EXITING => {
                if self.state.pos_fraction >= 0.5 {
                    match self.state.movement {
                        Movement::LEFT => { self.state.x -= 1},
                        Movement::RIGHT => { self.state.x += 1},
                        Movement::UP => { self.state.y -= 1},
                        Movement::DOWN => { self.state.y += 1},
                        _ => {}
                    };
                    self.state.pos_fraction = -0.4;
                    self.state.cell_move_state = CellMoveState::ENTERING;
                } else {
                    self.state.pos_fraction = partial_min(self.state.pos_fraction + 0.1, 0.5);
                }
            },
            CellMoveState::ENTERING => {
                if self.state.pos_fraction >= 0.0 {
                    self.state.pos_fraction = 0.0;
                    self.state.cell_move_state = CellMoveState::NEUTRAL;
                    self.state.movement = Movement::NEUTRAL;
                } else {
                    self.state.pos_fraction = partial_min(self.state.pos_fraction + 0.1, 0.0);
                }
            }
        }

    }

    pub fn collisions(&self, event_bus: &mut EventBus<GameEvent>, cell_state: (CellState, Vec<RcEntity>)) {
        self.entity_type.collisions(&self.state, event_bus, cell_state)
    }

    pub fn input(&mut self, key: Movement, adjacents: &Adjacents) {
        self.entity_type.input(&mut self.state, key, adjacents);
    }

    pub fn get_abs_position(&self) -> (i32, i32) {
        let (x_offset, y_offset) = match self.state.movement {
            Movement::NEUTRAL => (0.0, 0.0),
            Movement::LEFT => (-self.state.pos_fraction, 0.0),
            Movement::RIGHT => (self.state.pos_fraction, 0.0),
            Movement::UP => (0.0, -self.state.pos_fraction),
            Movement::DOWN => (0.0, self.state.pos_fraction),
        };
        (
            ((self.state.x * CELL_SIZE) as i32 + (CELL_SIZE as f64 * x_offset) as i32) as i32,
            ((self.state.y * CELL_SIZE) as i32 + (CELL_SIZE as f64 * y_offset) as i32) as i32,
        )
    }

    pub fn is_hard(&self) -> bool {
        self.entity_type.is_hard()
    }

    pub fn target_square(&self) -> Option<(u32, u32)> {
        match self.state.cell_move_state {
            CellMoveState::EXITING => {
                Some(match self.state.movement {
                    Movement::NEUTRAL => (self.state.x, self.state.y),
                    Movement::LEFT => (self.state.x - 1, self.state.y),
                    Movement::RIGHT => (self.state.x + 1, self.state.y),
                    Movement::UP => (self.state.x, self.state.y - 1),
                    Movement::DOWN => (self.state.x, self.state.y + 1),
                })
            },
            _ => None
        }
    }

    pub fn deflect(&mut self) {
        match self.state.cell_move_state {
            CellMoveState::EXITING => {
                self.state.cell_move_state = CellMoveState::ENTERING;
                self.state.pos_fraction = 1.0 - self.state.pos_fraction;
            },
            _ => ()
        }
    }

    pub fn destroy(&mut self) {
        self.state.destroyed = true;
    }

    pub fn push(&mut self, direction: Movement, tick: u64) {
        self.entity_type.push(direction, tick);
    }

    pub fn is_enterable(&self) -> bool {
        self.entity_type.is_enterable()
    }

    pub fn is_collectible(&self) -> bool {
        self.entity_type.is_collectible()
    }

    pub fn is_player(&self) -> bool {
        self.entity_type.is_player()
    }

    pub fn score(&self) -> u32 {
        self.entity_type.score()
    }

    pub fn open_exit(&mut self) {
        self.entity_type.open_exit(&mut self.state)
    }

    pub fn destructable(&self) -> bool {
        self.entity_type.destructable()
    }
}

pub type RcEntity = Rc<RefCell<Entity>>;
<|endoftext|>


<commit_before>mod rustnsobject;

extern crate objc;
extern crate objc_foundation;
extern crate cocoa;

extern crate fruitbasket;
use self::fruitbasket::FruitApp;

pub use ::TStatusBar;
pub use ::NSCallback;

use objc::runtime::Class;

use self::cocoa::base::{nil, YES};
use self::cocoa::appkit::NSStatusBar;
use self::cocoa::foundation::NSString;
use self::cocoa::appkit::{NSMenu,
                          NSMenuItem,
                          NSImage,
                          NSVariableStatusItemLength,
                          NSStatusItem,
                          NSButton};

use self::rustnsobject::{NSObj, NSObjTrait, NSObjCallbackTrait};

use std::sync::mpsc::Sender;
use std::ptr;
use std::ffi::CStr;

pub type Object = objc::runtime::Object;

pub struct OSXStatusBar {
    object: NSObj,
    app: FruitApp,
    status_bar_item: *mut objc::runtime::Object,
    menu_bar: *mut objc::runtime::Object,
}

impl TStatusBar for OSXStatusBar {
    type S = OSXStatusBar;
    fn new(tx: Sender<String>) -> OSXStatusBar {
        let mut bar;
        unsafe {
            let nsapp = FruitApp::new();
            nsapp.set_activation_policy(fruitbasket::ActivationPolicy::Prohibited);
            let status_bar = NSStatusBar::systemStatusBar(nil);
            bar = OSXStatusBar {
                app: nsapp,
                status_bar_item: status_bar.statusItemWithLength_(NSVariableStatusItemLength),
                menu_bar: NSMenu::new(nil),
                object: NSObj::alloc(tx),
            };

            // Default mode for menu bar items: blue highlight when selected
            let _: () = msg_send![bar.status_bar_item, setHighlightMode:YES];

            // Set title.  Only displayed if image fails to load.
            let title = NSString::alloc(nil).init_str("connectr");
            NSButton::setTitle_(bar.status_bar_item, title);
            let _: () = msg_send![title, release];

            // Look for icon in OS X bundle if there is one, otherwise current dir.
            // See docs/icons.md for explanation of icon files.
            // TODO: Use the full list of search paths.
            let icon_name = "connectr_80px_300dpi";
            let img_path = match fruitbasket::FruitApp::bundled_resource_path(icon_name, "png") {
                Some(path) => path,
                None => format!("{}.png", icon_name),
            };

            // Set the status bar image.  Switching on setTemplate switches it to
            // using OS X system-style icons that are masked to all white.  I
            // prefer color, but that should maybe be configurable.
            let img = NSString::alloc(nil).init_str(&img_path);
            let icon = NSImage::alloc(nil).initWithContentsOfFile_(img);
            #[cfg(feature = "mac_white_icon")]
            let _: () = msg_send![icon, setTemplate: YES]; // enable to make icon white
            bar.status_bar_item.button().setImage_(icon);
            let _: () = msg_send![img, release];
            let _: () = msg_send![icon, release];

            // Add the same image again as an alternate image.  I'm not sure how the
            // blending is performed, but it behaves differently and better if an
            // alt image is specified.  Without an alt image, the icon darkens too
            // much in 'dark mode' when selected, and is too light in 'light mode'.
            let img = NSString::alloc(nil).init_str(&img_path);
            let icon = NSImage::alloc(nil).initWithContentsOfFile_(img);
            let _: () = msg_send![bar.status_bar_item.button(), setAlternateImage: icon];
            let _: () = msg_send![img, release];
            let _: () = msg_send![icon, release];

            bar.status_bar_item.setMenu_(bar.menu_bar);
            bar.object.cb_fn = Some(Box::new(
                move |s, sender| {
                    let cb = s.get_value(sender);
                    cb(sender, &s.tx);
                }
            ));
        }
        bar
    }
    fn can_redraw(&mut self) -> bool {
        true
    }
    fn clear_items(&mut self) {
        unsafe {
            let old_menu = self.menu_bar;
            self.menu_bar = NSMenu::new(nil);
            self.status_bar_item.setMenu_(self.menu_bar);
            let _: () = msg_send![old_menu, removeAllItems];
            let _: () = msg_send![old_menu, release];
        }
    }
    fn set_tooltip(&mut self, text: &str) {
        unsafe {
            let img = NSString::alloc(nil).init_str(text);
            let _: () = msg_send![self.status_bar_item.button(), setToolTip: img];
            let _: () = msg_send![img, release];
        }
    }
    fn add_label(&mut self, label: &str) {
        unsafe {
            let txt = NSString::alloc(nil).init_str(label);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt, self.object.selector(), quit_key);
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.menu_bar.addItem_(app_menu_item);
            let _: () = msg_send![app_menu_item, release];
        }
    }
    fn add_quit(&mut self, label: &str) {
        unsafe {
            let txt = NSString::alloc(nil).init_str(label);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt, sel!(terminate:), quit_key);
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.menu_bar.addItem_(app_menu_item);
            let _: () = msg_send![app_menu_item, release];
        }
    }
    fn add_separator(&mut self) {
        unsafe {
            let cls = Class::get("NSMenuItem").unwrap();
            let sep: *mut Object = msg_send![cls, separatorItem];
            self.menu_bar.addItem_(sep);
        }
    }
    // TODO: whole API should accept menu option.  this whole thing should
    // be split out into its own recursive menu-builder trait.  this is
    // horrible.
    fn add_item(&mut self, menu: Option<*mut Object>,item: &str, callback: NSCallback, selected: bool) -> *mut Object {
        unsafe {
            let txt = NSString::alloc(nil).init_str(item);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt, self.object.selector(), quit_key);
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.object.add_callback(app_menu_item, callback);
            let objc = self.object.take_objc();
            let _: () = msg_send![app_menu_item, setTarget: objc];
            if selected {
                let _: () = msg_send![app_menu_item, setState: 1];
            }
            let item: *mut Object = app_menu_item;
            match menu {
                Some(menu) => { menu.addItem_(app_menu_item); },
                None => { self.menu_bar.addItem_(app_menu_item); }
            }
            let _: () = msg_send![app_menu_item, release];
            item
        }
    }
    fn add_submenu(&mut self, label: &str, callback: NSCallback) -> *mut Object {
        unsafe {
            let submenu = NSMenu::new(nil);
            let txt = NSString::alloc(nil).init_str(label);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt,
                                                     self.object.selector(),
                                                     quit_key);
            self.object.add_callback(app_menu_item, callback);
            let objc = self.object.take_objc();
            let _: () = msg_send![app_menu_item, setTarget: objc];
            let _: () = msg_send![app_menu_item, setSubmenu: submenu];
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.menu_bar.addItem_(app_menu_item);
            let _: () = msg_send![app_menu_item, release];
            submenu
        }
    }
    fn update_item(&mut self, item: *mut Object, label: &str) {
        unsafe {
            let ns_label = NSString::alloc(nil).init_str(label);
            let _: () = msg_send![item, setTitle: ns_label];
            let _: () = msg_send![ns_label, release];
        }
    }
    fn sel_item(&mut self, sender: u64) {
        let target: *mut Object = sender as *mut Object;
        unsafe {
            let _: () = msg_send![target, setState: 1];
        }
    }
    fn unsel_item(&mut self, sender: u64) {
        let target: *mut Object = sender as *mut Object;
        unsafe {
            let _: () = msg_send![target, setState: 0];
        }
    }
    fn run(&mut self, block: bool) {
        let period = match block {
            true => fruitbasket::RunPeriod::Forever,
            _ => fruitbasket::RunPeriod::Once,
        };
        let _ = self.app.run(period);
    }
}

//pub fn osx_alert(text: &str) {
//    unsafe {
//        let ns_text = NSString::alloc(nil).init_str(text);
//        let button = NSString::alloc(nil).init_str("ok");
//        let cls = Class::get("NSAlert").unwrap();
//        let alert: *mut Object = msg_send![cls, alloc];
//        let _: () = msg_send![alert, init];
//        let _: () = msg_send![alert, setMessageText: ns_text];
//        let _: () = msg_send![alert, addButtonWithTitle: button];
//        let _: () = msg_send![alert, runModal];
//        let _: () = msg_send![ns_text, release];
//        let _: () = msg_send![button, release];
//        let _: () = msg_send![alert, release];
//    }
//}

pub fn resource_dir() -> Option<String> {
    unsafe {
        let cls = Class::get("NSBundle").unwrap();
        let bundle: *mut Object = msg_send![cls, mainBundle];
        let path: *mut Object = msg_send![bundle, resourcePath];
        let cstr: *const i8 = msg_send![path, UTF8String];
        if cstr != ptr::null() {
            let rstr = CStr::from_ptr(cstr).to_string_lossy().into_owned();
            return Some(rstr);
        }
        None
    }
}
<commit_msg>fix memory leak: release submenus when cleared<commit_after>mod rustnsobject;

extern crate objc;
extern crate objc_foundation;
extern crate cocoa;

extern crate fruitbasket;
use self::fruitbasket::FruitApp;

pub use ::TStatusBar;
pub use ::NSCallback;

use objc::runtime::Class;

use self::cocoa::base::{nil, YES};
use self::cocoa::appkit::NSStatusBar;
use self::cocoa::foundation::NSString;
use self::cocoa::appkit::{NSMenu,
                          NSMenuItem,
                          NSImage,
                          NSVariableStatusItemLength,
                          NSStatusItem,
                          NSButton};

use self::rustnsobject::{NSObj, NSObjTrait, NSObjCallbackTrait};

use std::sync::mpsc::Sender;
use std::ptr;
use std::ffi::CStr;

pub type Object = objc::runtime::Object;

pub struct OSXStatusBar {
    object: NSObj,
    app: FruitApp,
    status_bar_item: *mut objc::runtime::Object,
    menu_bar: *mut objc::runtime::Object,
}

impl TStatusBar for OSXStatusBar {
    type S = OSXStatusBar;
    fn new(tx: Sender<String>) -> OSXStatusBar {
        let mut bar;
        unsafe {
            let nsapp = FruitApp::new();
            nsapp.set_activation_policy(fruitbasket::ActivationPolicy::Prohibited);
            let status_bar = NSStatusBar::systemStatusBar(nil);
            bar = OSXStatusBar {
                app: nsapp,
                status_bar_item: status_bar.statusItemWithLength_(NSVariableStatusItemLength),
                menu_bar: NSMenu::new(nil),
                object: NSObj::alloc(tx),
            };

            // Default mode for menu bar items: blue highlight when selected
            let _: () = msg_send![bar.status_bar_item, setHighlightMode:YES];

            // Set title.  Only displayed if image fails to load.
            let title = NSString::alloc(nil).init_str("connectr");
            NSButton::setTitle_(bar.status_bar_item, title);
            let _: () = msg_send![title, release];

            // Look for icon in OS X bundle if there is one, otherwise current dir.
            // See docs/icons.md for explanation of icon files.
            // TODO: Use the full list of search paths.
            let icon_name = "connectr_80px_300dpi";
            let img_path = match fruitbasket::FruitApp::bundled_resource_path(icon_name, "png") {
                Some(path) => path,
                None => format!("{}.png", icon_name),
            };

            // Set the status bar image.  Switching on setTemplate switches it to
            // using OS X system-style icons that are masked to all white.  I
            // prefer color, but that should maybe be configurable.
            let img = NSString::alloc(nil).init_str(&img_path);
            let icon = NSImage::alloc(nil).initWithContentsOfFile_(img);
            #[cfg(feature = "mac_white_icon")]
            let _: () = msg_send![icon, setTemplate: YES]; // enable to make icon white
            bar.status_bar_item.button().setImage_(icon);
            let _: () = msg_send![img, release];
            let _: () = msg_send![icon, release];

            // Add the same image again as an alternate image.  I'm not sure how the
            // blending is performed, but it behaves differently and better if an
            // alt image is specified.  Without an alt image, the icon darkens too
            // much in 'dark mode' when selected, and is too light in 'light mode'.
            let img = NSString::alloc(nil).init_str(&img_path);
            let icon = NSImage::alloc(nil).initWithContentsOfFile_(img);
            let _: () = msg_send![bar.status_bar_item.button(), setAlternateImage: icon];
            let _: () = msg_send![img, release];
            let _: () = msg_send![icon, release];

            bar.status_bar_item.setMenu_(bar.menu_bar);
            bar.object.cb_fn = Some(Box::new(
                move |s, sender| {
                    let cb = s.get_value(sender);
                    cb(sender, &s.tx);
                }
            ));
        }
        bar
    }
    fn can_redraw(&mut self) -> bool {
        true
    }
    fn clear_items(&mut self) {
        unsafe {
            let old_menu = self.menu_bar;
            self.menu_bar = NSMenu::new(nil);
            self.status_bar_item.setMenu_(self.menu_bar);
            let _: () = msg_send![old_menu, removeAllItems];
            let _: () = msg_send![old_menu, release];
        }
    }
    fn set_tooltip(&mut self, text: &str) {
        unsafe {
            let img = NSString::alloc(nil).init_str(text);
            let _: () = msg_send![self.status_bar_item.button(), setToolTip: img];
            let _: () = msg_send![img, release];
        }
    }
    fn add_label(&mut self, label: &str) {
        unsafe {
            let txt = NSString::alloc(nil).init_str(label);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt, self.object.selector(), quit_key);
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.menu_bar.addItem_(app_menu_item);
            let _: () = msg_send![app_menu_item, release];
        }
    }
    fn add_quit(&mut self, label: &str) {
        unsafe {
            let txt = NSString::alloc(nil).init_str(label);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt, sel!(terminate:), quit_key);
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.menu_bar.addItem_(app_menu_item);
            let _: () = msg_send![app_menu_item, release];
        }
    }
    fn add_separator(&mut self) {
        unsafe {
            let cls = Class::get("NSMenuItem").unwrap();
            let sep: *mut Object = msg_send![cls, separatorItem];
            self.menu_bar.addItem_(sep);
        }
    }
    // TODO: whole API should accept menu option.  this whole thing should
    // be split out into its own recursive menu-builder trait.  this is
    // horrible.
    fn add_item(&mut self, menu: Option<*mut Object>,item: &str, callback: NSCallback, selected: bool) -> *mut Object {
        unsafe {
            let txt = NSString::alloc(nil).init_str(item);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt, self.object.selector(), quit_key);
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.object.add_callback(app_menu_item, callback);
            let objc = self.object.take_objc();
            let _: () = msg_send![app_menu_item, setTarget: objc];
            if selected {
                let _: () = msg_send![app_menu_item, setState: 1];
            }
            let item: *mut Object = app_menu_item;
            match menu {
                Some(menu) => { menu.addItem_(app_menu_item); },
                None => { self.menu_bar.addItem_(app_menu_item); }
            }
            let _: () = msg_send![app_menu_item, release];
            item
        }
    }
    fn add_submenu(&mut self, label: &str, callback: NSCallback) -> *mut Object {
        unsafe {
            let submenu = NSMenu::new(nil);
            let txt = NSString::alloc(nil).init_str(label);
            let quit_key = NSString::alloc(nil).init_str("");
            let app_menu_item = NSMenuItem::alloc(nil)
                .initWithTitle_action_keyEquivalent_(txt,
                                                     self.object.selector(),
                                                     quit_key);
            self.object.add_callback(app_menu_item, callback);
            let objc = self.object.take_objc();
            let _: () = msg_send![app_menu_item, setTarget: objc];
            let _: () = msg_send![app_menu_item, setSubmenu: submenu];
            let _: () = msg_send![txt, release];
            let _: () = msg_send![quit_key, release];
            self.menu_bar.addItem_(app_menu_item);
            let _: () = msg_send![app_menu_item, release];
            let _: () = msg_send![submenu, release];
            submenu
        }
    }
    fn update_item(&mut self, item: *mut Object, label: &str) {
        unsafe {
            let ns_label = NSString::alloc(nil).init_str(label);
            let _: () = msg_send![item, setTitle: ns_label];
            let _: () = msg_send![ns_label, release];
        }
    }
    fn sel_item(&mut self, sender: u64) {
        let target: *mut Object = sender as *mut Object;
        unsafe {
            let _: () = msg_send![target, setState: 1];
        }
    }
    fn unsel_item(&mut self, sender: u64) {
        let target: *mut Object = sender as *mut Object;
        unsafe {
            let _: () = msg_send![target, setState: 0];
        }
    }
    fn run(&mut self, block: bool) {
        let period = match block {
            true => fruitbasket::RunPeriod::Forever,
            _ => fruitbasket::RunPeriod::Once,
        };
        let _ = self.app.run(period);
    }
}

//pub fn osx_alert(text: &str) {
//    unsafe {
//        let ns_text = NSString::alloc(nil).init_str(text);
//        let button = NSString::alloc(nil).init_str("ok");
//        let cls = Class::get("NSAlert").unwrap();
//        let alert: *mut Object = msg_send![cls, alloc];
//        let _: () = msg_send![alert, init];
//        let _: () = msg_send![alert, setMessageText: ns_text];
//        let _: () = msg_send![alert, addButtonWithTitle: button];
//        let _: () = msg_send![alert, runModal];
//        let _: () = msg_send![ns_text, release];
//        let _: () = msg_send![button, release];
//        let _: () = msg_send![alert, release];
//    }
//}

pub fn resource_dir() -> Option<String> {
    unsafe {
        let cls = Class::get("NSBundle").unwrap();
        let bundle: *mut Object = msg_send![cls, mainBundle];
        let path: *mut Object = msg_send![bundle, resourcePath];
        let cstr: *const i8 = msg_send![path, UTF8String];
        if cstr != ptr::null() {
            let rstr = CStr::from_ptr(cstr).to_string_lossy().into_owned();
            return Some(rstr);
        }
        None
    }
}
<|endoftext|>


<commit_before>//! Define results and error. `Result<T, CabotError>`
use std::error::Error;
use std::fmt::{self, Display};
use std::io::Error as IOError;
use std::string::FromUtf8Error;

use rustls::TLSError;
use url::ParseError as UrlParseError;

#[derive(Debug)]
/// Errors in cabot
pub enum CabotError {
    IOError(IOError),
    CertificateError(TLSError),
    UrlParseError(UrlParseError),
    DNSLookupError(String),
    SchemeError(String),
    HostnameParseError(String),
    OpaqueUrlError(String),
    HttpResponseParseError(String),
    EncodingError(FromUtf8Error),
}

impl Display for CabotError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = match self {
            CabotError::SchemeError(scheme) => format!("Unmanaged Scheme: {}", scheme),
            CabotError::OpaqueUrlError(url) => format!("Opaque URL Error: {}", url),
            CabotError::HostnameParseError(name) => format!("Invalid Hostname: {}", name),
            CabotError::UrlParseError(err) => format!("URL Parse Error: {}", err),
            CabotError::IOError(err) => format!("IO Error: {}", err),
            CabotError::DNSLookupError(err) => format!("DNS Lookup Error: {}", err),
            CabotError::CertificateError(err) => format!("Certificate Error: {}", err),
            // Unexpexcted Error, not used
            CabotError::HttpResponseParseError(err) => format!("HTTP Response Parse Error: {}", err),
            CabotError::EncodingError(err) => format!("Utf8 Encoding Error: {}", err),
        };
        write!(f, "{}", description)
    }
}

impl Error for CabotError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        let err: Option<&(dyn Error + 'static)> = match self {
            CabotError::UrlParseError(err) => Some(err),
            CabotError::IOError(err) => Some(err),
            CabotError::CertificateError(err) => Some(err),
            CabotError::EncodingError(err) => Some(err),
            _ => None
        };
        err
    }
}

impl From<IOError> for CabotError {
    fn from(err: IOError) -> CabotError {
        CabotError::IOError(err)
    }
}

impl From<TLSError> for CabotError {
    fn from(err: TLSError) -> CabotError {
        CabotError::CertificateError(err)
    }
}

impl From<UrlParseError> for CabotError {
    fn from(err: UrlParseError) -> CabotError {
        CabotError::UrlParseError(err)
    }
}

impl From<FromUtf8Error> for CabotError {
    fn from(err: FromUtf8Error) -> CabotError {
        CabotError::EncodingError(err)
    }
}
<commit_msg>Reorder errors<commit_after>//! Define results and error. `Result<T, CabotError>`
use std::error::Error;
use std::fmt::{self, Display};
use std::io::Error as IOError;
use std::string::FromUtf8Error;

use rustls::TLSError;
use url::ParseError as UrlParseError;

#[derive(Debug)]
/// Errors in cabot
pub enum CabotError {
    DNSLookupError(String),
    HostnameParseError(String),
    HttpResponseParseError(String),
    OpaqueUrlError(String),
    SchemeError(String),
    // Wrapped errors
    CertificateError(TLSError),
    EncodingError(FromUtf8Error),
    IOError(IOError),
    UrlParseError(UrlParseError),
}

impl Display for CabotError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = match self {
            CabotError::DNSLookupError(err) => format!("DNS Lookup Error: {}", err),
            CabotError::HostnameParseError(name) => format!("Invalid Hostname: {}", name),
            CabotError::HttpResponseParseError(err) => format!("HTTP Response Parse Error: {}", err),
            CabotError::OpaqueUrlError(url) => format!("Opaque URL Error: {}", url),
            CabotError::SchemeError(scheme) => format!("Unmanaged Scheme: {}", scheme),
            // Wrapped errors
            CabotError::CertificateError(err) => format!("Certificate Error: {}", err),
            CabotError::EncodingError(err) => format!("Utf8 Encoding Error: {}", err),
            CabotError::IOError(err) => format!("IO Error: {}", err),
            CabotError::UrlParseError(err) => format!("URL Parse Error: {}", err),
        };
        write!(f, "{}", description)
    }
}

impl Error for CabotError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        let err: Option<&(dyn Error + 'static)> = match self {
            CabotError::CertificateError(err) => Some(err),
            CabotError::EncodingError(err) => Some(err),
            CabotError::IOError(err) => Some(err),
            CabotError::UrlParseError(err) => Some(err),
            _ => None
        };
        err
    }
}

impl From<TLSError> for CabotError {
    fn from(err: TLSError) -> CabotError {
        CabotError::CertificateError(err)
    }
}

impl From<FromUtf8Error> for CabotError {
    fn from(err: FromUtf8Error) -> CabotError {
        CabotError::EncodingError(err)
    }
}

impl From<IOError> for CabotError {
    fn from(err: IOError) -> CabotError {
        CabotError::IOError(err)
    }
}

impl From<UrlParseError> for CabotError {
    fn from(err: UrlParseError) -> CabotError {
        CabotError::UrlParseError(err)
    }
}
<|endoftext|>


<commit_before>//! Error management module

#![allow(missing_docs)]

/// The error type used by this crate.
#[derive(Fail, Debug)]
pub enum Error {
    #[fail(display = "I/O error")]
    Io(#[cause] ::std::io::Error),

    #[fail(display = "UTF8 error")]
    Utf8(#[cause] ::std::str::Utf8Error),

    #[fail(display = "Unexpected EOF during reading {}.", _0)]
    UnexpectedEof(String),

    #[fail(display = "Expecting </{}> found </{}>", expected, found)]
    EndEventMismatch { expected: String, found: String },

    #[fail(display = "Unexpected token '{}'", _0)]
    UnexpectedToken(String),

    #[fail(display = "Only Comment, CDATA and DOCTYPE nodes can start with a '!'")]
    UnexpectedBang,

    #[fail(display = "Cannot read text, expecting Event::Text")]
    TextNotFound,

    #[fail(
        display = "XmlDecl must start with 'version' attribute, found {:?}",
        _0
    )]
    XmlDeclWithoutVersion(Option<String>),

    #[fail(
        display = "error while parsing attribute at position {}: Attribute key cannot contain quote.",
        _0
    )]
    NameWithQuote(usize),

    #[fail(
        display = "error while parsing attribute at position {}: Attribute key must be directly followed by = or space",
        _0
    )]
    NoEqAfterName(usize),

    #[fail(
        display = "error while parsing attribute at position {}: Attribute value must start with a quote.",
        _0
    )]
    UnquotedValue(usize),

    #[fail(
        display = "error while parsing attribute at position {}: Duplicate attribute at position {} and {}",
        _0, _1, _0
    )]
    DuplicatedAttribute(usize, usize),

    #[fail(display = "{}", _0)]
    EscapeError(#[cause] ::escape::EscapeError),
}

/// A specialized `Result` type where the error is hard-wired to [`Error`].
///
/// [`Error`]: enum.Error.html
pub type Result<T> = ::std::result::Result<T, Error>;
<commit_msg>add standard type conversions for Error<commit_after>//! Error management module

#![allow(missing_docs)]

/// The error type used by this crate.
#[derive(Fail, Debug)]
pub enum Error {
    #[fail(display = "I/O error")]
    Io(#[cause] ::std::io::Error),

    #[fail(display = "UTF8 error")]
    Utf8(#[cause] ::std::str::Utf8Error),

    #[fail(display = "Unexpected EOF during reading {}.", _0)]
    UnexpectedEof(String),

    #[fail(display = "Expecting </{}> found </{}>", expected, found)]
    EndEventMismatch { expected: String, found: String },

    #[fail(display = "Unexpected token '{}'", _0)]
    UnexpectedToken(String),

    #[fail(display = "Only Comment, CDATA and DOCTYPE nodes can start with a '!'")]
    UnexpectedBang,

    #[fail(display = "Cannot read text, expecting Event::Text")]
    TextNotFound,

    #[fail(
        display = "XmlDecl must start with 'version' attribute, found {:?}",
        _0
    )]
    XmlDeclWithoutVersion(Option<String>),

    #[fail(
        display = "error while parsing attribute at position {}: Attribute key cannot contain quote.",
        _0
    )]
    NameWithQuote(usize),

    #[fail(
        display = "error while parsing attribute at position {}: Attribute key must be directly followed by = or space",
        _0
    )]
    NoEqAfterName(usize),

    #[fail(
        display = "error while parsing attribute at position {}: Attribute value must start with a quote.",
        _0
    )]
    UnquotedValue(usize),

    #[fail(
        display = "error while parsing attribute at position {}: Duplicate attribute at position {} and {}",
        _0, _1, _0
    )]
    DuplicatedAttribute(usize, usize),

    #[fail(display = "{}", _0)]
    EscapeError(#[cause] ::escape::EscapeError),
}

impl From<::std::io::Error> for Error {
    /// Creates a new `Error::Io` from the given error
    #[inline]
    fn from(error: ::std::io::Error) -> Error {
        Error::Io(error)
    }
}

impl From<::std::str::Utf8Error> for Error {
    /// Creates a new `Error::Utf8` from the given error
    #[inline]
    fn from(error: ::std::str::Utf8Error) -> Error {
        Error::Utf8(error)
    }
}

/// A specialized `Result` type where the error is hard-wired to [`Error`].
///
/// [`Error`]: enum.Error.html
pub type Result<T> = ::std::result::Result<T, Error>;
<|endoftext|>


<commit_before>use std::fmt::{ self, Write };

pub struct HrefEscaper<W: Write> {
  inner: W,
}

impl<W: Write> HrefEscaper<W> {
  pub fn new(inner: W) -> HrefEscaper<W> {
    HrefEscaper {
      inner: inner,
    }
  }
}

static HREF_SAFE: [bool; 0x80] = [
  false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
  false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
  false,  true, false,  true,  true,  true, false, false,  true,  true,  true,  true,  true,  true,  true,  true,
   true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true, false,  true, false,  true,
   true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
   true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true, false, false, false, false,  true,
  false,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
   true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true, false, false, false, false, false,
];

impl<W: Write> Write for HrefEscaper<W> {
  fn write_str(&mut self, s: &str) -> fmt::Result {
    for c in s.chars() {
      match c {
        '&' => {
          try!(self.inner.write_str("&"));
        },
        '\'' => {
          try!(self.inner.write_str("'"));
        },
        c if c > '\x7F' || !HREF_SAFE[c as usize] => {
          unimplemented!(); // Need to convert to hex escape string
        },
        _ => {
          try!(self.inner.write_char(c));
        },
      }
    }
    Ok(())
  }
}
<commit_msg>Auto merge of #4 - Nemo157:href-escaper, r=Nemo157<commit_after>use std::fmt::{ self, Write };

pub struct HrefEscaper<W: Write> {
  inner: W,
}

impl<W: Write> HrefEscaper<W> {
  pub fn new(inner: W) -> HrefEscaper<W> {
    HrefEscaper {
      inner: inner,
    }
  }
}

fn char_to_string(c: char) -> String {
  let mut s = String::with_capacity(4);
  s.push(c);
  s
}

fn percent_encode(c: char) -> String {
  let mut s = String::with_capacity(9);
  for b in char_to_string(c).as_bytes() {
    write!(s, "%{0:X}", b).unwrap();
  }
  s
}

// See https://url.spec.whatwg.org/ for escaping rules
fn escape_char(c: char) -> String {
  if c < '!' {
    // Control characters and space
    return percent_encode(c);
  }

  if c <= '\x7F' {
    // Disallowed ASCII characters
    return match c {
      '"' | '%' | '<' | '>' | '[' | '\\' => percent_encode(c),
      ']' | '^' | '`' | '{' | '|' | '}' | '\x7F' => percent_encode(c),
      _ => char_to_string(c),
    };
  }

  match c {
    // Allowed Unicode ranges
    '\u{00A0}'...'\u{D7FF}'
      | '\u{E000}'...'\u{FDCF}'
      | '\u{FDF0}'...'\u{FFFD}'
      | '\u{10000}'...'\u{1FFFD}'
      | '\u{20000}'...'\u{2FFFD}'
      | '\u{30000}'...'\u{3FFFD}'
      | '\u{40000}'...'\u{4FFFD}'
      | '\u{50000}'...'\u{5FFFD}'
      | '\u{60000}'...'\u{6FFFD}'
      | '\u{70000}'...'\u{7FFFD}'
      | '\u{80000}'...'\u{8FFFD}'
      | '\u{90000}'...'\u{9FFFD}'
      | '\u{A0000}'...'\u{AFFFD}'
      | '\u{B0000}'...'\u{BFFFD}'
      | '\u{C0000}'...'\u{CFFFD}'
      | '\u{D0000}'...'\u{DFFFD}'
      | '\u{E0000}'...'\u{EFFFD}'
      | '\u{F0000}'...'\u{FFFFD}'
      | '\u{100000}'...'\u{10FFFD}' => char_to_string(c),
    _ => percent_encode(c),
  }
}

impl<W: Write> Write for HrefEscaper<W> {
  fn write_str(&mut self, s: &str) -> fmt::Result {
    for c in s.chars() {
      try!(self.write_char(c));
    }
    Ok(())
  }

  fn write_char(&mut self, c: char) -> fmt::Result {
    self.inner.write_str(&*escape_char(c))
  }
}

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

  macro_rules! test_url {
    ($url:expr, $expected:expr) => ({
      use std::fmt::Write;
      let mut s = String::with_capacity(31);
      HrefEscaper::new(&mut s).write_str($url).unwrap();
      assert_eq!(&*s, $expected);
    })
  }

  #[test]
  fn test_poop() {
    test_url!("https://example.org/💩", "https://example.org/💩");
  }

  #[test]
  fn test_ascii() {
    test_url!("https://example.org/something", "https://example.org/something");
  }

  #[test]
  fn test_ascii_escapes() {
    test_url!("https://example.org/\"", "https://example.org/%22");
    test_url!("https://example.org/%", "https://example.org/%25");
    test_url!("https://example.org/<", "https://example.org/%3C");
    test_url!("https://example.org/>", "https://example.org/%3E");
    test_url!("https://example.org/[", "https://example.org/%5B");
    test_url!("https://example.org/\\", "https://example.org/%5C");
    test_url!("https://example.org/]", "https://example.org/%5D");
    test_url!("https://example.org/^", "https://example.org/%5E");
    test_url!("https://example.org/`", "https://example.org/%60");
    test_url!("https://example.org/{", "https://example.org/%7B");
    test_url!("https://example.org/|", "https://example.org/%7C");
    test_url!("https://example.org/}", "https://example.org/%7D");
  }

  #[test]
  fn test_unicode_escapes() {
    test_url!("https://example.org/\u{92}", "https://example.org/%C2%92");
    test_url!("https://example.org/\u{FFFFE}", "https://example.org/%F3%BF%BF%BE");
  }
}
<|endoftext|>


<commit_before>extern crate num_cpus;

use std;
use std::thread;
use std::sync::mpsc;

static CORE_MUL_THRESHOLD: usize = 8;

pub fn prime_filter(iter_size: usize) -> Vec<bool>{
    if iter_size<100{
        slow_prime_filter(iter_size)
    }else{
        prime_filter_section(0, iter_size)
    }
}

fn case_1(y_sq: usize, iter_size: usize)
    -> Vec<usize> {
    //n_1 = 4x^2 + y^2 === 1 (mod 4)
    let (mut n_1, mut to_next_n_1) = (y_sq, 4);    let mut ret_stack: Vec<usize> = Vec::new();
    loop{
        n_1 += to_next_n_1;
        to_next_n_1 += 8;
        match n_1%60{
            _ if n_1 >= iter_size => break,
            1 | 13 | 17 | 29 | 37 | 41 | 49 | 53 => (),
            _ => continue,
        };

        // println!("1: {}", n_1);
        ret_stack.push(n_1);
    };
    ret_stack
}
fn case_2(y_sq: usize, iter_size: usize)
    -> Vec<usize> {
    //n_2 = 3x^2 + y^2 === 1 (mod 6)
    let (mut n_2, mut to_next_n_2) = (y_sq, 3);
    let mut ret_stack: Vec<usize> = Vec::new();
    loop {
        n_2 += to_next_n_2;
        to_next_n_2 += 6;
        match n_2%60{
            _ if n_2 >= iter_size => break,
            7 | 19 | 31 | 43 => (),
            _ => continue,
        };
        // println!("2: {}", n_2);
        ret_stack.push(n_2);
    };
    ret_stack
}
fn case_3(y_sq: usize, to_next_y_sq: usize, iter_size: usize)
    -> Vec<usize> {
    //n_3 = 3x^2 - y^2 === 11 (mod 12)
    let (mut n_3, mut to_next_n_3) = (2*y_sq, 3*to_next_y_sq);
    let mut ret_stack: Vec<usize> = Vec::new();
    loop {
        n_3 += to_next_n_3;
        to_next_n_3 += 6;
        match n_3%60{
            _ if n_3 >= iter_size => break,
            11 | 23 | 47 | 59 => (),
            _ => continue,
        };
        // println!("3: {}", n_3);
        ret_stack.push(n_3);
    };
    ret_stack
}

fn prime_filter_section(min:usize, max: usize) -> Vec<bool>{
    //Sieve of Atkin
    let num_cpus = num_cpus::get();
    let thread_threshold = num_cpus*CORE_MUL_THRESHOLD;
    assert!(min<max);
    let mut prime_filter = vec![false; max-min];
    if (min <= 2) & (max > 2) {prime_filter[2-min] = true;}
    if (min <= 3) & (max > 3) {prime_filter[3-min] = true;}
    if (min <= 5) & (max > 5) {prime_filter[5-min] = true;}

    let (mut y_sq, mut to_next_y_sq) = match min {
        0|1 => (1, 3),
        _ => (min*min, 2*min + 1),
    };
    let mut spawned_threads = 0;
    let (tx, rx) = mpsc::channel();
    while y_sq<max {
        if y_sq%2 == 1 {
            spawned_threads += 1;
            let (y_sq, tx) = (y_sq, tx.clone());
            thread::spawn(move || {
                tx.send(case_1(y_sq, max)).unwrap();
            });
        };
        if y_sq%3 == 1 {

            spawned_threads += 1;
            let (y_sq, to_next_y_sq, tx1) = (y_sq, to_next_y_sq, tx.clone());
            thread::spawn(move || {
                tx1.send(case_2(y_sq, max)).unwrap();
            });
            if y_sq*2<max{
                spawned_threads += 1;
                let tx2 = tx.clone();
                thread::spawn(move || {
                    tx2.send(case_3(y_sq, to_next_y_sq, max)).unwrap();
                });
            }
        };
        while{ //Do-while
            y_sq += to_next_y_sq;
            to_next_y_sq += 2;
            y_sq%6 == 0
        } {};
        if spawned_threads >= thread_threshold{
            // let prev_spawned_threads = spawned_threads;
            while spawned_threads > num_cpus{
                for mes in rx.try_iter(){
                    spawned_threads -= 1;
                    for flip_i in mes{
                        prime_filter[flip_i - min] ^= true;
                    }
                }
            }
            // let despawned_threads = prev_spawned_threads - spawned_threads;
            // println!("despawned_threads: {}", despawned_threads);
        };
    };

    while spawned_threads!=0{
        for mes in rx.try_iter(){
            spawned_threads -= 1;
            for flip_i in mes{
                prime_filter[flip_i - min] ^= true;
            }
        }
    };
    //Eliminate non-squarefree numbers
    let mut n_sq = 49; // 7^2
    let mut next_n_sq = 32; //9^2 - 7^2, skip even numbers.
    while n_sq < max {
        let mut non_sq_free = n_sq;
        while non_sq_free < max {
            if non_sq_free >= min {
                prime_filter[non_sq_free - min] = false;
            }
            while{ //Do-while
                non_sq_free += n_sq + n_sq;
                (non_sq_free%3==0) | (non_sq_free%5==0)
            } {};
        };
        while{ //Do-while
            n_sq += next_n_sq;
            next_n_sq += 8;
            (n_sq%3==0) | (n_sq%5 == 0)
        } {};
    }
    prime_filter
}
#[cfg(test)]
pub fn old_prime_filter(iter_size: usize) -> std::vec::Vec<bool>{
    slow_prime_filter(iter_size)
}

fn slow_prime_filter(iter_size: usize) -> std::vec::Vec<bool>{
    if iter_size < 5 {
         let mut ret = vec![false, false, true, true];
         ret.truncate(iter_size);
         return ret
     }
    let mut prime_filter = vec![true; iter_size];
    prime_filter[0] = false;
    prime_filter[1] = false;
    let mut cur_num = 2;
    'outer: loop{
        for i in (cur_num+1)..iter_size{
            if 0 == i%cur_num { prime_filter[i] = false; }
        }
        cur_num += 1;
        while !prime_filter[cur_num]{
            if cur_num*cur_num > iter_size {
                break 'outer
            }
            cur_num += 1;
        }
    };
    prime_filter
}
<commit_msg>Use only thread_threshold<commit_after>extern crate num_cpus;

use std;
use std::thread;
use std::sync::mpsc;

static CORE_MUL_THRESHOLD: usize = 16;

pub fn prime_filter(iter_size: usize) -> Vec<bool>{
    if iter_size<100{
        slow_prime_filter(iter_size)
    }else{
        prime_filter_section(0, iter_size)
    }
}

fn case_1(y_sq: usize, iter_size: usize)
    -> Vec<usize> {
    //n_1 = 4x^2 + y^2 === 1 (mod 4)
    let (mut n_1, mut to_next_n_1) = (y_sq, 4);    let mut ret_stack: Vec<usize> = Vec::new();
    loop{
        n_1 += to_next_n_1;
        to_next_n_1 += 8;
        match n_1%60{
            _ if n_1 >= iter_size => break,
            1 | 13 | 17 | 29 | 37 | 41 | 49 | 53 => (),
            _ => continue,
        };

        // println!("1: {}", n_1);
        ret_stack.push(n_1);
    };
    ret_stack
}
fn case_2(y_sq: usize, iter_size: usize)
    -> Vec<usize> {
    //n_2 = 3x^2 + y^2 === 1 (mod 6)
    let (mut n_2, mut to_next_n_2) = (y_sq, 3);
    let mut ret_stack: Vec<usize> = Vec::new();
    loop {
        n_2 += to_next_n_2;
        to_next_n_2 += 6;
        match n_2%60{
            _ if n_2 >= iter_size => break,
            7 | 19 | 31 | 43 => (),
            _ => continue,
        };
        // println!("2: {}", n_2);
        ret_stack.push(n_2);
    };
    ret_stack
}
fn case_3(y_sq: usize, to_next_y_sq: usize, iter_size: usize)
    -> Vec<usize> {
    //n_3 = 3x^2 - y^2 === 11 (mod 12)
    let (mut n_3, mut to_next_n_3) = (2*y_sq, 3*to_next_y_sq);
    let mut ret_stack: Vec<usize> = Vec::new();
    loop {
        n_3 += to_next_n_3;
        to_next_n_3 += 6;
        match n_3%60{
            _ if n_3 >= iter_size => break,
            11 | 23 | 47 | 59 => (),
            _ => continue,
        };
        // println!("3: {}", n_3);
        ret_stack.push(n_3);
    };
    ret_stack
}

fn prime_filter_section(min:usize, max: usize) -> Vec<bool>{
    //Sieve of Atkin
    let num_cpus = num_cpus::get();
    let thread_threshold = num_cpus*CORE_MUL_THRESHOLD;
    assert!(min<max);
    let mut prime_filter = vec![false; max-min];
    if (min <= 2) & (max > 2) {prime_filter[2-min] = true;}
    if (min <= 3) & (max > 3) {prime_filter[3-min] = true;}
    if (min <= 5) & (max > 5) {prime_filter[5-min] = true;}

    let (mut y_sq, mut to_next_y_sq) = match min {
        0|1 => (1, 3),
        _ => (min*min, 2*min + 1),
    };
    let mut spawned_threads = 0;
    let (tx, rx) = mpsc::channel();
    while y_sq<max {
        if y_sq%2 == 1 {
            spawned_threads += 1;
            let (y_sq, tx) = (y_sq, tx.clone());
            thread::spawn(move || {
                tx.send(case_1(y_sq, max)).unwrap();
            });
        };
        if y_sq%3 == 1 {

            spawned_threads += 1;
            let (y_sq, to_next_y_sq, tx1) = (y_sq, to_next_y_sq, tx.clone());
            thread::spawn(move || {
                tx1.send(case_2(y_sq, max)).unwrap();
            });
            if y_sq*2<max{
                spawned_threads += 1;
                let tx2 = tx.clone();
                thread::spawn(move || {
                    tx2.send(case_3(y_sq, to_next_y_sq, max)).unwrap();
                });
            }
        };
        while{ //Do-while
            y_sq += to_next_y_sq;
            to_next_y_sq += 2;
            y_sq%6 == 0
        } {};
        while spawned_threads >= thread_threshold{
            // let prev_spawned_threads = spawned_threads;
            for mes in rx.try_iter(){
                spawned_threads -= 1;
                for flip_i in mes{
                    prime_filter[flip_i - min] ^= true;
                }
            }
            // let despawned_threads = prev_spawned_threads - spawned_threads;
            // println!("despawned_threads: {}", despawned_threads);
        };
    };
    // println!("Threads after first loop: {}", spawned_threads);
    while spawned_threads!=0{
        match rx.recv(){
            Ok(mes) => {
                spawned_threads -= 1;
                for flip_i in mes{
                    prime_filter[flip_i - min] ^= true;
                }
            },
            Err(e) => panic!(e.to_string()),
        };
    };
    //Eliminate non-squarefree numbers
    let mut n_sq = 49; // 7^2
    let mut next_n_sq = 32; //9^2 - 7^2, skip even numbers.
    while n_sq < max {
        let mut non_sq_free = n_sq;
        while non_sq_free < max {
            if non_sq_free >= min {
                prime_filter[non_sq_free - min] = false;
            }
            while{ //Do-while
                non_sq_free += n_sq + n_sq;
                (non_sq_free%3==0) | (non_sq_free%5==0)
            } {};
        };
        while{ //Do-while
            n_sq += next_n_sq;
            next_n_sq += 8;
            (n_sq%3==0) | (n_sq%5 == 0)
        } {};
    }
    prime_filter
}
#[cfg(test)]
pub fn old_prime_filter(iter_size: usize) -> std::vec::Vec<bool>{
    slow_prime_filter(iter_size)
}

fn slow_prime_filter(iter_size: usize) -> std::vec::Vec<bool>{
    if iter_size < 5 {
         let mut ret = vec![false, false, true, true];
         ret.truncate(iter_size);
         return ret
     }
    let mut prime_filter = vec![true; iter_size];
    prime_filter[0] = false;
    prime_filter[1] = false;
    let mut cur_num = 2;
    'outer: loop{
        for i in (cur_num+1)..iter_size{
            if 0 == i%cur_num { prime_filter[i] = false; }
        }
        cur_num += 1;
        while !prime_filter[cur_num]{
            if cur_num*cur_num > iter_size {
                break 'outer
            }
            cur_num += 1;
        }
    };
    prime_filter
}
<|endoftext|>


<commit_before>
use syntax::Token;

const SPACES_CYCLE : [char; 3] = ['\n', '\t', ' '];

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenToken {
    Spaces(String),
    CommentMono(String),
}

pub struct LexGen {
    list : Vec<Vec<GenToken>>,
    size : usize,
}

pub fn new() -> LexGen {
    LexGen{
        list : vec![vec![]],
        size : 0,
    }
}

fn length_list(src : &Vec<GenToken>) -> usize{
    src.iter().fold(0, |sum, ref val| {sum + length_token(val)})
}

fn length_token(token : &GenToken) -> usize{
    match token {
        &GenToken::Spaces(ref s) => s.len(),
        &GenToken::CommentMono(ref s) => "//".len() + s.len(),
    }
}

impl LexGen {

    fn enumerate(&mut self) -> Vec<GenToken> {
        loop {
            match self.list.pop() {
                None => {
                    self.size += 1;
                    self.list.push(vec![]);
                },
                Some(elem) => {
                    if length_list(&elem) == self.size {
                        return elem;
                    } else if length_list(&elem) > self.size {
                        continue;
                    } else {

                        for c in SPACES_CYCLE.iter() {
                            let mut new = elem.clone();
                            match elem.last() {
                                Some(&GenToken::CommentMono(_)) => {
                                    new.push(GenToken::Spaces("\n".to_string()));
                                },
                                _ => {},
                            }
                            new.push(GenToken::Spaces(c.to_string()));
                            self.list.push(new);
                        }

                        let mut new = elem.clone();
                        match elem.last() {
                            Some(&GenToken::CommentMono(_)) => {
                                new.push(GenToken::Spaces("\n".to_string()));
                            },
                            _ => {},
                        }
                        new.push(GenToken::CommentMono("".to_string()));
                        self.list.push(new);

                        let mut new = elem.clone();
                        match elem.last() {
                            Some(&GenToken::CommentMono(_)) => {
                                new.push(GenToken::Spaces("\n".to_string()));
                            },
                            _ => {},
                        }
                        new.push(GenToken::CommentMono("A".to_string()));
                        self.list.push(new);

                        continue;
                    }
                }
            }
        }
    }

}

fn generate(res : &Vec<GenToken>) -> (String, Vec<Token>) {

    let mut res_str = String::new();
    let mut res_tok = vec![];
    let mut before = 0;

    for t in res.clone() {
        match t {
            GenToken::Spaces(s) => {
                res_str.push_str(&s);
                match res_tok.last() {
                    Some(& Token::Spaces(a,b)) =>  {
                        res_tok.pop();
                        res_tok.push(Token::Spaces(a,b+s.len()));
                        // TODO possible to modify in place with a mutable pattern ?
                    }
                    _ => {
                        res_tok.push( Token::Spaces(before, res_str.len()) );
                    }
                }
                before = res_str.len();
            }
            GenToken::CommentMono(s) => {
                res_str.push_str("//");
                res_str.push_str(&s);
                let len = res_str.len();
                res_tok.push( Token::Comment(before, len) );
                before = len;
            }
        }
    }

    (res_str, res_tok)
}

impl Iterator for LexGen {
    type Item = (String,Vec<Token>);

    fn next(&mut self) -> Option<(String,Vec<Token>)> {
        let res = self.enumerate();
        let res = Some(generate(&res));
        res
    }
}

#[test]
// Really a bad way to test, maybe I can just check is some
// outputs are present
fn test_gen() {
    let mut gen = new();
    assert_eq!(gen.next(), Some((
        "".to_string(), vec![
        ])));
    assert_eq!(gen.next(), Some((
        " ".to_string(), vec![
            Token::Spaces(0,1),
        ])));
    assert_eq!(gen.next(), Some((
        "\t".to_string(), vec![
            Token::Spaces(0,1),
        ])));
    assert_eq!(gen.next(), Some((
        "\n".to_string(), vec![
            Token::Spaces(0,1),
        ])));

    assert_eq!(gen.next(), Some((
        "//".to_string(), vec![
            Token::Comment(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        "  ".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        " \t".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        " \n".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        "\t ".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        "\t\t".to_string(), vec![
            Token::Spaces(0,2),
        ])));
}

//
//         Scanner Properties
//
// Tokens are a partition of the source
// tokens are not empty
// No Consequent Spaces
// Spaces contains only white spaces
// Comments either:
//   - start with "//" contains no '\n' and next token start with '\n';
//   - or start with "/*" and ends with "*/", contains no inner "*/"
//     (accept multi-comments inside multi-comment?)
//

<commit_msg>generate mono-line comment always followed by newline<commit_after>
use syntax::Token;

const SPACES_CYCLE : [char; 3] = ['\n', '\t', ' '];

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenToken {
    Spaces(String),
    CommentMono(String),
}

pub struct LexGen {
    list : Vec<Vec<GenToken>>,
    size : usize,
}

pub fn new() -> LexGen {
    LexGen{
        list : vec![vec![]],
        size : 0,
    }
}

fn length_list(src : &Vec<GenToken>) -> usize{
    src.iter().fold(0, |sum, ref val| {sum + length_token(val)})
}

fn length_token(token : &GenToken) -> usize{
    match token {
        &GenToken::Spaces(ref s) => s.len(),
        &GenToken::CommentMono(ref s) => "//".len() + s.len(),
    }
}

impl LexGen {

    fn enumerate(&mut self) -> Vec<GenToken> {
        loop {
            match self.list.pop() {
                None => {
                    self.size += 1;
                    self.list.push(vec![]);
                },
                Some(elem) => {
                    if length_list(&elem) == self.size {
                        return elem;
                    } else if length_list(&elem) > self.size {
                        continue;
                    } else {

                        for c in SPACES_CYCLE.iter() {
                            let mut new = elem.clone();
                            new.push(GenToken::Spaces(c.to_string()));
                            self.list.push(new);
                        }

                        let mut new = elem.clone();
                        new.push(GenToken::CommentMono("".to_string()));
                        new.push(GenToken::Spaces("\n".to_string()));
                        self.list.push(new);

                        let mut new = elem.clone();
                        new.push(GenToken::CommentMono("A".to_string()));
                        new.push(GenToken::Spaces("\n".to_string()));
                        self.list.push(new);

                        continue;
                    }
                }
            }
        }
    }

}

fn generate(res : &Vec<GenToken>) -> (String, Vec<Token>) {

    let mut res_str = String::new();
    let mut res_tok = vec![];
    let mut before = 0;

    for t in res.clone() {
        match t {
            GenToken::Spaces(s) => {
                res_str.push_str(&s);
                match res_tok.last() {
                    Some(& Token::Spaces(a,b)) =>  {
                        res_tok.pop();
                        res_tok.push(Token::Spaces(a,b+s.len()));
                        // TODO possible to modify in place with a mutable pattern ?
                    }
                    _ => {
                        res_tok.push( Token::Spaces(before, res_str.len()) );
                    }
                }
                before = res_str.len();
            }
            GenToken::CommentMono(s) => {
                res_str.push_str("//");
                res_str.push_str(&s);
                let len = res_str.len();
                res_tok.push( Token::Comment(before, len) );
                before = len;
            }
        }
    }

    (res_str, res_tok)
}

impl Iterator for LexGen {
    type Item = (String,Vec<Token>);

    fn next(&mut self) -> Option<(String,Vec<Token>)> {
        let res = self.enumerate();
        let res = Some(generate(&res));
        res
    }
}

#[test]
// Really a bad way to test, maybe I can just check is some
// outputs are present
fn test_gen() {
    let mut gen = new();
    assert_eq!(gen.next(), Some((
        "".to_string(), vec![
        ])));
    assert_eq!(gen.next(), Some((
        " ".to_string(), vec![
            Token::Spaces(0,1),
        ])));
    assert_eq!(gen.next(), Some((
        "\t".to_string(), vec![
            Token::Spaces(0,1),
        ])));
    assert_eq!(gen.next(), Some((
        "\n".to_string(), vec![
            Token::Spaces(0,1),
        ])));

    assert_eq!(gen.next(), Some((
        "  ".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        " \t".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        " \n".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        "\t ".to_string(), vec![
            Token::Spaces(0,2),
        ])));
    assert_eq!(gen.next(), Some((
        "\t\t".to_string(), vec![
            Token::Spaces(0,2),
        ])));
}

//
//         Scanner Properties
//
// Tokens are a partition of the source
// tokens are not empty
// No Consequent Spaces
// Spaces contains only white spaces
// Comments either:
//   - start with "//" contains no '\n' and next token start with '\n';
//   - or start with "/*" and ends with "*/", contains no inner "*/"
//     (accept multi-comments inside multi-comment?)
//

<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.

// Objects for seeking through a char stream for occurrences of TODO and FIXME.
// Depending on the loaded configuration, may also check that these have an
// associated issue number.

use std::fmt;

pub use config::ReportTactic;

const TO_DO_CHARS: &[char] = &['T', 'O', 'D', 'O'];
const FIX_ME_CHARS: &[char] = &['F', 'I', 'X', 'M', 'E'];

// Enabled implementation detail is here because it is
// irrelevant outside the issues module
impl ReportTactic {
    fn is_enabled(&self) -> bool {
        *self != ReportTactic::Never
    }
}

#[derive(Clone, Copy)]
enum Seeking {
    Issue { todo_idx: usize, fixme_idx: usize },
    Number { issue: Issue, part: NumberPart },
}

#[derive(Clone, Copy)]
enum NumberPart {
    OpenParen,
    Pound,
    Number,
    CloseParen,
}

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct Issue {
    issue_type: IssueType,
    // Indicates whether we're looking for issues with missing numbers, or
    // all issues of this type.
    missing_number: bool,
}

impl fmt::Display for Issue {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let msg = match self.issue_type {
            IssueType::Todo => "TODO",
            IssueType::Fixme => "FIXME",
        };
        let details = if self.missing_number {
            " without issue number"
        } else {
            ""
        };

        write!(fmt, "{}{}", msg, details)
    }
}

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum IssueType {
    Todo,
    Fixme,
}

enum IssueClassification {
    Good,
    Bad(Issue),
    None,
}

pub struct BadIssueSeeker {
    state: Seeking,
    report_todo: ReportTactic,
    report_fixme: ReportTactic,
}

impl BadIssueSeeker {
    pub fn new(report_todo: ReportTactic, report_fixme: ReportTactic) -> BadIssueSeeker {
        BadIssueSeeker {
            state: Seeking::Issue {
                todo_idx: 0,
                fixme_idx: 0,
            },
            report_todo: report_todo,
            report_fixme: report_fixme,
        }
    }

    // Check whether or not the current char is conclusive evidence for an
    // unnumbered TO-DO or FIX-ME.
    pub fn inspect(&mut self, c: char) -> Option<Issue> {
        match self.state {
            Seeking::Issue {
                todo_idx,
                fixme_idx,
            } => {
                self.state = self.inspect_issue(c, todo_idx, fixme_idx);
            }
            Seeking::Number { issue, part } => {
                let result = self.inspect_number(c, issue, part);

                if let IssueClassification::None = result {
                    return None;
                }

                self.state = Seeking::Issue {
                    todo_idx: 0,
                    fixme_idx: 0,
                };

                if let IssueClassification::Bad(issue) = result {
                    return Some(issue);
                }
            }
        }

        None
    }

    fn inspect_issue(&mut self, c: char, mut todo_idx: usize, mut fixme_idx: usize) -> Seeking {
        // FIXME: Should we also check for lower case characters?
        if self.report_todo.is_enabled() && c == TO_DO_CHARS[todo_idx] {
            todo_idx += 1;
            if todo_idx == TO_DO_CHARS.len() {
                return Seeking::Number {
                    issue: Issue {
                        issue_type: IssueType::Todo,
                        missing_number: if let ReportTactic::Unnumbered = self.report_todo {
                            true
                        } else {
                            false
                        },
                    },
                    part: NumberPart::OpenParen,
                };
            }
            fixme_idx = 0;
        } else if self.report_fixme.is_enabled() && c == FIX_ME_CHARS[fixme_idx] {
            // Exploit the fact that the character sets of todo and fixme
            // are disjoint by adding else.
            fixme_idx += 1;
            if fixme_idx == FIX_ME_CHARS.len() {
                return Seeking::Number {
                    issue: Issue {
                        issue_type: IssueType::Fixme,
                        missing_number: if let ReportTactic::Unnumbered = self.report_fixme {
                            true
                        } else {
                            false
                        },
                    },
                    part: NumberPart::OpenParen,
                };
            }
            todo_idx = 0;
        } else {
            todo_idx = 0;
            fixme_idx = 0;
        }

        Seeking::Issue {
            todo_idx: todo_idx,
            fixme_idx: fixme_idx,
        }
    }

    fn inspect_number(
        &mut self,
        c: char,
        issue: Issue,
        mut part: NumberPart,
    ) -> IssueClassification {
        if !issue.missing_number || c == '\n' {
            return IssueClassification::Bad(issue);
        } else if c == ')' {
            return if let NumberPart::CloseParen = part {
                IssueClassification::Good
            } else {
                IssueClassification::Bad(issue)
            };
        }

        match part {
            NumberPart::OpenParen => {
                if c != '(' {
                    return IssueClassification::Bad(issue);
                } else {
                    part = NumberPart::Pound;
                }
            }
            NumberPart::Pound => {
                if c == '#' {
                    part = NumberPart::Number;
                }
            }
            NumberPart::Number => {
                if c >= '0' && c <= '9' {
                    part = NumberPart::CloseParen;
                } else {
                    return IssueClassification::Bad(issue);
                }
            }
            NumberPart::CloseParen => {}
        }

        self.state = Seeking::Number {
            part: part,
            issue: issue,
        };

        IssueClassification::None
    }
}

#[test]
fn find_unnumbered_issue() {
    fn check_fail(text: &str, failing_pos: usize) {
        let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
        assert_eq!(
            Some(failing_pos),
            text.chars().position(|c| seeker.inspect(c).is_some())
        );
    }

    fn check_pass(text: &str) {
        let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
        assert_eq!(None, text.chars().position(|c| seeker.inspect(c).is_some()));
    }

    check_fail("TODO\n", 4);
    check_pass(" TO FIX DOME\n");
    check_fail(" \n FIXME\n", 8);
    check_fail("FIXME(\n", 6);
    check_fail("FIXME(#\n", 7);
    check_fail("FIXME(#1\n", 8);
    check_fail("FIXME(#)1\n", 7);
    check_pass("FIXME(#1222)\n");
    check_fail("FIXME(#12\n22)\n", 9);
    check_pass("FIXME(@maintainer, #1222, hello)\n");
    check_fail("TODO(#22) FIXME\n", 15);
}

#[test]
fn find_issue() {
    fn is_bad_issue(text: &str, report_todo: ReportTactic, report_fixme: ReportTactic) -> bool {
        let mut seeker = BadIssueSeeker::new(report_todo, report_fixme);
        text.chars().any(|c| seeker.inspect(c).is_some())
    }

    assert!(is_bad_issue(
        "TODO(@maintainer, #1222, hello)\n",
        ReportTactic::Always,
        ReportTactic::Never,
    ));

    assert!(!is_bad_issue(
        "TODO: no number\n",
        ReportTactic::Never,
        ReportTactic::Always,
    ));

    assert!(is_bad_issue(
        "This is a FIXME(#1)\n",
        ReportTactic::Never,
        ReportTactic::Always,
    ));

    assert!(!is_bad_issue(
        "bad FIXME\n",
        ReportTactic::Always,
        ReportTactic::Never,
    ));
}

#[test]
fn issue_type() {
    let mut seeker = BadIssueSeeker::new(ReportTactic::Always, ReportTactic::Never);
    let expected = Some(Issue {
        issue_type: IssueType::Todo,
        missing_number: false,
    });

    assert_eq!(
        expected,
        "TODO(#100): more awesomeness"
            .chars()
            .map(|c| seeker.inspect(c))
            .find(Option::is_some)
            .unwrap()
    );

    let mut seeker = BadIssueSeeker::new(ReportTactic::Never, ReportTactic::Unnumbered);
    let expected = Some(Issue {
        issue_type: IssueType::Fixme,
        missing_number: true,
    });

    assert_eq!(
        expected,
        "Test. FIXME: bad, bad, not good"
            .chars()
            .map(|c| seeker.inspect(c))
            .find(Option::is_some)
            .unwrap()
    );
}
<commit_msg>case insensitive FIXME and TODO<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.

// Objects for seeking through a char stream for occurrences of TODO and FIXME.
// Depending on the loaded configuration, may also check that these have an
// associated issue number.

use std::fmt;

pub use config::ReportTactic;

const TO_DO_CHARS: &[char] = &['t', 'o', 'd', 'o'];
const FIX_ME_CHARS: &[char] = &['f', 'i', 'x', 'm', 'e'];

// Enabled implementation detail is here because it is
// irrelevant outside the issues module
impl ReportTactic {
    fn is_enabled(&self) -> bool {
        *self != ReportTactic::Never
    }
}

#[derive(Clone, Copy)]
enum Seeking {
    Issue { todo_idx: usize, fixme_idx: usize },
    Number { issue: Issue, part: NumberPart },
}

#[derive(Clone, Copy)]
enum NumberPart {
    OpenParen,
    Pound,
    Number,
    CloseParen,
}

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct Issue {
    issue_type: IssueType,
    // Indicates whether we're looking for issues with missing numbers, or
    // all issues of this type.
    missing_number: bool,
}

impl fmt::Display for Issue {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let msg = match self.issue_type {
            IssueType::Todo => "TODO",
            IssueType::Fixme => "FIXME",
        };
        let details = if self.missing_number {
            " without issue number"
        } else {
            ""
        };

        write!(fmt, "{}{}", msg, details)
    }
}

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum IssueType {
    Todo,
    Fixme,
}

enum IssueClassification {
    Good,
    Bad(Issue),
    None,
}

pub struct BadIssueSeeker {
    state: Seeking,
    report_todo: ReportTactic,
    report_fixme: ReportTactic,
}

impl BadIssueSeeker {
    pub fn new(report_todo: ReportTactic, report_fixme: ReportTactic) -> BadIssueSeeker {
        BadIssueSeeker {
            state: Seeking::Issue {
                todo_idx: 0,
                fixme_idx: 0,
            },
            report_todo: report_todo,
            report_fixme: report_fixme,
        }
    }

    // Check whether or not the current char is conclusive evidence for an
    // unnumbered TO-DO or FIX-ME.
    pub fn inspect(&mut self, c: char) -> Option<Issue> {
        match self.state {
            Seeking::Issue {
                todo_idx,
                fixme_idx,
            } => {
                self.state = self.inspect_issue(c, todo_idx, fixme_idx);
            }
            Seeking::Number { issue, part } => {
                let result = self.inspect_number(c, issue, part);

                if let IssueClassification::None = result {
                    return None;
                }

                self.state = Seeking::Issue {
                    todo_idx: 0,
                    fixme_idx: 0,
                };

                if let IssueClassification::Bad(issue) = result {
                    return Some(issue);
                }
            }
        }

        None
    }

    fn inspect_issue(&mut self, c: char, mut todo_idx: usize, mut fixme_idx: usize) -> Seeking {
        if let Some(lower_case_c) = c.to_lowercase().next() {
            if self.report_todo.is_enabled() && lower_case_c == TO_DO_CHARS[todo_idx] {
                todo_idx += 1;
                if todo_idx == TO_DO_CHARS.len() {
                    return Seeking::Number {
                        issue: Issue {
                            issue_type: IssueType::Todo,
                            missing_number: if let ReportTactic::Unnumbered = self.report_todo {
                                true
                            } else {
                                false
                            },
                        },
                        part: NumberPart::OpenParen,
                    };
                }
                fixme_idx = 0;
            } else if self.report_fixme.is_enabled() && lower_case_c == FIX_ME_CHARS[fixme_idx] {
                // Exploit the fact that the character sets of todo and fixme
                // are disjoint by adding else.
                fixme_idx += 1;
                if fixme_idx == FIX_ME_CHARS.len() {
                    return Seeking::Number {
                        issue: Issue {
                            issue_type: IssueType::Fixme,
                            missing_number: if let ReportTactic::Unnumbered = self.report_fixme {
                                true
                            } else {
                                false
                            },
                        },
                        part: NumberPart::OpenParen,
                    };
                }
                todo_idx = 0;
            } else {
                todo_idx = 0;
                fixme_idx = 0;
            }
        }

        Seeking::Issue {
            todo_idx: todo_idx,
            fixme_idx: fixme_idx,
        }
    }

    fn inspect_number(
        &mut self,
        c: char,
        issue: Issue,
        mut part: NumberPart,
    ) -> IssueClassification {
        if !issue.missing_number || c == '\n' {
            return IssueClassification::Bad(issue);
        } else if c == ')' {
            return if let NumberPart::CloseParen = part {
                IssueClassification::Good
            } else {
                IssueClassification::Bad(issue)
            };
        }

        match part {
            NumberPart::OpenParen => {
                if c != '(' {
                    return IssueClassification::Bad(issue);
                } else {
                    part = NumberPart::Pound;
                }
            }
            NumberPart::Pound => {
                if c == '#' {
                    part = NumberPart::Number;
                }
            }
            NumberPart::Number => {
                if c >= '0' && c <= '9' {
                    part = NumberPart::CloseParen;
                } else {
                    return IssueClassification::Bad(issue);
                }
            }
            NumberPart::CloseParen => {}
        }

        self.state = Seeking::Number {
            part: part,
            issue: issue,
        };

        IssueClassification::None
    }
}

#[test]
fn find_unnumbered_issue() {
    fn check_fail(text: &str, failing_pos: usize) {
        let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
        assert_eq!(
            Some(failing_pos),
            text.chars().position(|c| seeker.inspect(c).is_some())
        );
    }

    fn check_pass(text: &str) {
        let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
        assert_eq!(None, text.chars().position(|c| seeker.inspect(c).is_some()));
    }

    check_fail("TODO\n", 4);
    check_pass(" TO FIX DOME\n");
    check_fail(" \n FIXME\n", 8);
    check_fail("FIXME(\n", 6);
    check_fail("FIXME(#\n", 7);
    check_fail("FIXME(#1\n", 8);
    check_fail("FIXME(#)1\n", 7);
    check_pass("FIXME(#1222)\n");
    check_fail("FIXME(#12\n22)\n", 9);
    check_pass("FIXME(@maintainer, #1222, hello)\n");
    check_fail("TODO(#22) FIXME\n", 15);
}

#[test]
fn find_issue() {
    fn is_bad_issue(text: &str, report_todo: ReportTactic, report_fixme: ReportTactic) -> bool {
        let mut seeker = BadIssueSeeker::new(report_todo, report_fixme);
        text.chars().any(|c| seeker.inspect(c).is_some())
    }

    assert!(is_bad_issue(
        "TODO(@maintainer, #1222, hello)\n",
        ReportTactic::Always,
        ReportTactic::Never,
    ));

    assert!(!is_bad_issue(
        "TODO: no number\n",
        ReportTactic::Never,
        ReportTactic::Always,
    ));

    assert!(!is_bad_issue(
        "Todo: mixed case\n",
        ReportTactic::Never,
        ReportTactic::Always,
    ));

    assert!(is_bad_issue(
        "This is a FIXME(#1)\n",
        ReportTactic::Never,
        ReportTactic::Always,
    ));

    assert!(is_bad_issue(
        "This is a FixMe(#1) mixed case\n",
        ReportTactic::Never,
        ReportTactic::Always,
    ));

    assert!(!is_bad_issue(
        "bad FIXME\n",
        ReportTactic::Always,
        ReportTactic::Never,
    ));
}

#[test]
fn issue_type() {
    let mut seeker = BadIssueSeeker::new(ReportTactic::Always, ReportTactic::Never);
    let expected = Some(Issue {
        issue_type: IssueType::Todo,
        missing_number: false,
    });

    assert_eq!(
        expected,
        "TODO(#100): more awesomeness"
            .chars()
            .map(|c| seeker.inspect(c))
            .find(Option::is_some)
            .unwrap()
    );

    let mut seeker = BadIssueSeeker::new(ReportTactic::Never, ReportTactic::Unnumbered);
    let expected = Some(Issue {
        issue_type: IssueType::Fixme,
        missing_number: true,
    });

    assert_eq!(
        expected,
        "Test. FIXME: bad, bad, not good"
            .chars()
            .map(|c| seeker.inspect(c))
            .find(Option::is_some)
            .unwrap()
    );
}
<|endoftext|>