<commit_before>extern crate graphics;
extern crate piston;
extern crate opengl_graphics;
extern crate freetype;

use piston::event::*;
use piston::input::*;
use opengl_graphics::GlGraphics;

use field;
use font;
use settings;

struct Vec2f {
    x: f64,
    y: f64
}

pub struct App {
    settings: settings::Settings,
    mouse_coords: Vec2f,
    field: field::Field,
    selected_cell: Option<field::Coords>,
    conflicting_cell: Option<field::Coords>
}

impl App {
    pub fn new(settings: settings::Settings) -> App {
        App {
            settings: settings,
            mouse_coords: Vec2f{ x: 0.0, y: 0.0 },
            field: field::Field::new(),
            selected_cell: None,
            conflicting_cell: None
        }
    }

    pub fn on_render(&mut self, args: &RenderArgs,
                     gl: &mut GlGraphics, face: &mut freetype::Face) {
        gl.draw(args.viewport(), |c, g| {
            use graphics::*;
            clear([1.0; 4], g);

            let pointed_cell = field::Coords{
                x: (self.mouse_coords.x / self.settings.cell_size.x as f64)
                    .floor() as u8,
                y: (self.mouse_coords.y / self.settings.cell_size.y as f64)
                    .floor() as u8 };
            rectangle([0.95, 0.95, 0.95, 1.0],
                      [(pointed_cell.x as f64) * self.settings.cell_size.x,
                       (pointed_cell.y as f64) * self.settings.cell_size.y,
                       self.settings.cell_size.x, self.settings.cell_size.y],
                      c.transform, g);

            for y in 0..9 {
                for x in 0..9 {
                    let cell = self.field.get_cell(x, y);
                    if cell.fixed {
                        rectangle([0.9, 0.9, 0.9, 1.0],
                            [(x as f64) * self.settings.cell_size.x,
                             (y as f64) * self.settings.cell_size.y,
                             self.settings.cell_size.x,
                             self.settings.cell_size.y],
                            c.transform, g);
                    }
                }
            }

            if let Some(ref cell) = self.selected_cell {
                if let Some(digit) = self.field.get_cell(cell.x, cell.y).digit {
                    for y in 0..9 {
                        for x in 0..9 {
                            if let Some(other_digit) =
                                    self.field.get_cell(x, y).digit {
                                if other_digit == digit {
                                    rectangle([0.8, 0.8, 0.9, 1.0],
                                        [(x as f64) * self.settings.cell_size.x,
                                         (y as f64) * self.settings.cell_size.y,
                                         self.settings.cell_size.x,
                                         self.settings.cell_size.y],
                                        c.transform, g);
                                }
                            }
                        }
                    }
                }
            }

            if let Some(ref cell) = self.conflicting_cell {
                rectangle([0.9, 0.8, 0.8, 1.0],
                          [(cell.x as f64) * self.settings.cell_size.x,
                           (cell.y as f64) * self.settings.cell_size.y,
                           self.settings.cell_size.x, self.settings.cell_size.y],
                          c.transform, g);
            }

            if let Some(ref cell) = self.selected_cell {
                rectangle([0.8, 0.9, 0.8, 1.0],
                          [(cell.x as f64) * self.settings.cell_size.x,
                           (cell.y as f64) * self.settings.cell_size.y,
                           self.settings.cell_size.x, self.settings.cell_size.y],
                          c.transform, g);
            }

            for y in 0..9 {
                for x in 0..9 {
                    if let Some(ref digit) = self.field.cells[y][x].digit {
                        let transform = c.transform.trans(
                            (x as f64) * self.settings.cell_size.x +
                                self.settings.text_offset.x,
                            (y as f64) * self.settings.cell_size.y +
                                self.settings.text_offset.y);
                        font::render_text(face, g, transform,
                                    &digit.to_string());
                    }
                }
            }

            for n in 1..9 {
                let mut thick = 2.0;
                if n % 3 == 0 {
                    thick = 6.0;
                }
                rectangle([0.0, 0.0, 0.0, 1.0],
                          [(n as f64) * self.settings.cell_size.x - thick / 2.0,
                           0.0, thick / 2.0, self.settings.wind_size.y],
                           c.transform, g);
                rectangle([0.0, 0.0, 0.0, 1.0],
                          [0.0, (n as f64) * self.settings.cell_size.y -
                                thick / 2.0,
                           self.settings.wind_size.x, thick / 2.0],
                           c.transform, g);
            }
        });
    }

    pub fn on_button_press(&mut self, button: &Button) {
        match button {
            &Button::Keyboard(key) => {
                self.on_key_down(&key);
            },
            &Button::Mouse(button) => {
                self.on_mouse_click(&button);
            }
        }
    }

    fn on_key_down(&mut self, pressed_key: &Key) {
        let key_digit_mapping = [
            (Key::D1, 1), (Key::D2, 2), (Key::D3, 3),
            (Key::D4, 4), (Key::D5, 5), (Key::D6, 6),
            (Key::D7, 7), (Key::D8, 8), (Key::D9, 9) ];
        for &(key, digit) in key_digit_mapping.iter() {
            if pressed_key == &key {
                if let Some(ref cell) = self.selected_cell {
                    if !self.field.get_cell(cell.x, cell.y).fixed {
                        match self.field.find_conflicts(cell, digit) {
                            Some(coords) => {
                                self.conflicting_cell = Some(coords);
                            },
                            None => {
                                self.field.get_cell(cell.x, cell.y).digit =
                                    Some(digit);
                                self.conflicting_cell = None;
                            }
                        }
                    }
                }
            }
        }
        if pressed_key == &Key::Backspace {
            if let Some(ref cell) = self.selected_cell {
                if !self.field.get_cell(cell.x, cell.y).fixed {
                    self.field.get_cell(cell.x, cell.y).digit = None;
                    self.conflicting_cell = None;
                }
            }
        }
        if pressed_key == &Key::S {
            self.field.fill_solution();
        }
        if pressed_key == &Key::R {
            self.field.fill_random();
        }
    }

    fn on_mouse_click(&mut self, button: &MouseButton) {
        if let &MouseButton::Left = button {
            self.selected_cell = Some(field::Coords{
                x: (self.mouse_coords.x / self.settings.cell_size.x) as u8,
                y: (self.mouse_coords.y / self.settings.cell_size.y) as u8 });
        }
    }

    pub fn on_mouse_move(&mut self, args: &[f64; 2]) {
        self.mouse_coords.x = args[0];
        self.mouse_coords.y = args[1];
    }
}
<commit_msg>Adjust line thickness<commit_after>extern crate graphics;
extern crate piston;
extern crate opengl_graphics;
extern crate freetype;

use piston::event::*;
use piston::input::*;
use opengl_graphics::GlGraphics;

use field;
use font;
use settings;

struct Vec2f {
    x: f64,
    y: f64
}

pub struct App {
    settings: settings::Settings,
    mouse_coords: Vec2f,
    field: field::Field,
    selected_cell: Option<field::Coords>,
    conflicting_cell: Option<field::Coords>
}

impl App {
    pub fn new(settings: settings::Settings) -> App {
        App {
            settings: settings,
            mouse_coords: Vec2f{ x: 0.0, y: 0.0 },
            field: field::Field::new(),
            selected_cell: None,
            conflicting_cell: None
        }
    }

    pub fn on_render(&mut self, args: &RenderArgs,
                     gl: &mut GlGraphics, face: &mut freetype::Face) {
        gl.draw(args.viewport(), |c, g| {
            use graphics::*;
            clear([1.0; 4], g);

            let pointed_cell = field::Coords{
                x: (self.mouse_coords.x / self.settings.cell_size.x as f64)
                    .floor() as u8,
                y: (self.mouse_coords.y / self.settings.cell_size.y as f64)
                    .floor() as u8 };
            rectangle([0.95, 0.95, 0.95, 1.0],
                      [(pointed_cell.x as f64) * self.settings.cell_size.x,
                       (pointed_cell.y as f64) * self.settings.cell_size.y,
                       self.settings.cell_size.x, self.settings.cell_size.y],
                      c.transform, g);

            for y in 0..9 {
                for x in 0..9 {
                    let cell = self.field.get_cell(x, y);
                    if cell.fixed {
                        rectangle([0.9, 0.9, 0.9, 1.0],
                            [(x as f64) * self.settings.cell_size.x,
                             (y as f64) * self.settings.cell_size.y,
                             self.settings.cell_size.x,
                             self.settings.cell_size.y],
                            c.transform, g);
                    }
                }
            }

            if let Some(ref cell) = self.selected_cell {
                if let Some(digit) = self.field.get_cell(cell.x, cell.y).digit {
                    for y in 0..9 {
                        for x in 0..9 {
                            if let Some(other_digit) =
                                    self.field.get_cell(x, y).digit {
                                if other_digit == digit {
                                    rectangle([0.8, 0.8, 0.9, 1.0],
                                        [(x as f64) * self.settings.cell_size.x,
                                         (y as f64) * self.settings.cell_size.y,
                                         self.settings.cell_size.x,
                                         self.settings.cell_size.y],
                                        c.transform, g);
                                }
                            }
                        }
                    }
                }
            }

            if let Some(ref cell) = self.conflicting_cell {
                rectangle([0.9, 0.8, 0.8, 1.0],
                          [(cell.x as f64) * self.settings.cell_size.x,
                           (cell.y as f64) * self.settings.cell_size.y,
                           self.settings.cell_size.x, self.settings.cell_size.y],
                          c.transform, g);
            }

            if let Some(ref cell) = self.selected_cell {
                rectangle([0.8, 0.9, 0.8, 1.0],
                          [(cell.x as f64) * self.settings.cell_size.x,
                           (cell.y as f64) * self.settings.cell_size.y,
                           self.settings.cell_size.x, self.settings.cell_size.y],
                          c.transform, g);
            }

            for y in 0..9 {
                for x in 0..9 {
                    if let Some(ref digit) = self.field.cells[y][x].digit {
                        let transform = c.transform.trans(
                            (x as f64) * self.settings.cell_size.x +
                                self.settings.text_offset.x,
                            (y as f64) * self.settings.cell_size.y +
                                self.settings.text_offset.y);
                        font::render_text(face, g, transform,
                                    &digit.to_string());
                    }
                }
            }

            for n in 1..9 {
                let mut thick = 2.0;
                if n % 3 == 0 {
                    thick = 8.0;
                }
                rectangle([0.0, 0.0, 0.0, 1.0],
                          [(n as f64) * self.settings.cell_size.x - thick / 2.0,
                           0.0, thick / 2.0, self.settings.wind_size.y],
                           c.transform, g);
                rectangle([0.0, 0.0, 0.0, 1.0],
                          [0.0, (n as f64) * self.settings.cell_size.y -
                                thick / 2.0,
                           self.settings.wind_size.x, thick / 2.0],
                           c.transform, g);
            }
        });
    }

    pub fn on_button_press(&mut self, button: &Button) {
        match button {
            &Button::Keyboard(key) => {
                self.on_key_down(&key);
            },
            &Button::Mouse(button) => {
                self.on_mouse_click(&button);
            }
        }
    }

    fn on_key_down(&mut self, pressed_key: &Key) {
        let key_digit_mapping = [
            (Key::D1, 1), (Key::D2, 2), (Key::D3, 3),
            (Key::D4, 4), (Key::D5, 5), (Key::D6, 6),
            (Key::D7, 7), (Key::D8, 8), (Key::D9, 9) ];
        for &(key, digit) in key_digit_mapping.iter() {
            if pressed_key == &key {
                if let Some(ref cell) = self.selected_cell {
                    if !self.field.get_cell(cell.x, cell.y).fixed {
                        match self.field.find_conflicts(cell, digit) {
                            Some(coords) => {
                                self.conflicting_cell = Some(coords);
                            },
                            None => {
                                self.field.get_cell(cell.x, cell.y).digit =
                                    Some(digit);
                                self.conflicting_cell = None;
                            }
                        }
                    }
                }
            }
        }
        if pressed_key == &Key::Backspace {
            if let Some(ref cell) = self.selected_cell {
                if !self.field.get_cell(cell.x, cell.y).fixed {
                    self.field.get_cell(cell.x, cell.y).digit = None;
                    self.conflicting_cell = None;
                }
            }
        }
        if pressed_key == &Key::S {
            self.field.fill_solution();
        }
        if pressed_key == &Key::R {
            self.field.fill_random();
        }
    }

    fn on_mouse_click(&mut self, button: &MouseButton) {
        if let &MouseButton::Left = button {
            self.selected_cell = Some(field::Coords{
                x: (self.mouse_coords.x / self.settings.cell_size.x) as u8,
                y: (self.mouse_coords.y / self.settings.cell_size.y) as u8 });
        }
    }

    pub fn on_mouse_move(&mut self, args: &[f64; 2]) {
        self.mouse_coords.x = args[0];
        self.mouse_coords.y = args[1];
    }
}
<|endoftext|>


<commit_before>use std::fs;
use std::io;
use std::cmp::{Ordering, PartialOrd};
use std::path::Path;
use needed::Needed;
use regex::Regex;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use telegram;
use std::default::Default;
use std::io::Write;
use rustc_serialize::json;

#[derive(PartialEq, Eq, Debug)]
pub enum LogLevel {
    Debug,
    Info,
    Warning,
    Error,
}

impl LogLevel {
    pub fn prefix(&self) -> &str {
        use self::LogLevel::*;
        match *self {
            Debug => "DEBUG",
            Info => "INFO ",
            Warning => "WARN ",
            Error => "ERROR",
        }
    }

    fn as_num(&self) -> u8 {
        use self::LogLevel::*;
        match *self {
            Debug   =>  0,
            Info    => 10,
            Warning => 20,
            Error   => 30,
        }
    }
}

impl PartialOrd for LogLevel {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.as_num().partial_cmp(&other.as_num())
    }
}

struct Logger {
    logfile: Option<fs::File>,
    loglevel: LogLevel,
}

impl Logger {
    pub fn log(&mut self, lvl: LogLevel, msg: &str) {
        use term_painter::{Attr, ToStyle};
        use term_painter::Color::*;
        use self::LogLevel::*;

        if lvl < self.loglevel {
            return;
        }

        if let Some(ref mut file) = self.logfile {
            if let Err(e) = write!(file, "[{}] {}\n", lvl.prefix(), msg) {
                panic!("Error occured while writing log file: {}\n{:?}", e, e);
            }
        }

        let prefix = match lvl {
            Error   => Attr::Bold.fg(Red),
            Warning => Attr::Bold.fg(Yellow),
            Info    => Attr::Plain.fg(White),
            Debug   => Attr::Dim.fg(NotSet),
        };
        let text = match lvl {
            Error   => Attr::Bold.fg(Red),
            Warning => Attr::Plain.fg(Yellow),
            Info    => Attr::Plain.fg(NotSet),
            Debug   => Attr::Plain.fg(NotSet),
        };

        println!("[{}] {}", prefix.paint(lvl.prefix()), text.paint(msg));
    }
}

macro_rules! log {
    ($this:ident, $lvl:ident: $fmt:expr) => {
        $this.logger.log(LogLevel::$lvl, $fmt);
    };
    ($this:ident, $lvl:ident: $fmt:expr, $($arg:tt)*) => {
        $this.logger.log(LogLevel::$lvl, &*format!($fmt, $($arg)*));
    };
}

// Data per FlatShare
#[derive(Default, RustcEncodable, RustcDecodable)]
struct FlatShare {
    needed: Needed,
}

// Maps a Telegram 'ChatID' to the corresponding flatshare data
pub type FlatMap = HashMap<telegram::Integer, FlatShare>;

pub struct MartiniBot {
    api: Arc<Mutex<telegram::Bot>>,
    me: telegram::User,
    flats: FlatMap,
    logger: Logger,
    data_dir: String,
}

pub struct BotBuilder<'a> {
    token: String,
    logfile: Option<&'a Path>,
    loglevel: LogLevel,
    data_dir: String,
}

/// Type for building a bot easily
impl<'a> BotBuilder<'a> {
    /// Specifies a logfile to log into. By default the bot does not log
    /// into a file.
    pub fn with_logfile<'b>(self, path: &'b Path) -> BotBuilder<'b> {
        BotBuilder{
            token: self.token,
            logfile: Some(path),
            loglevel: self.loglevel,
            data_dir: self.data_dir,
        }
    }

    /// Specifies the log level of the bot. All messages with level higher or
    /// equal to the specified level will be logged.
    pub fn with_loglevel(mut self, lvl: LogLevel) -> Self {
        self.loglevel = lvl;
        self
    }

    /// Specify the directory where the flatshare data lives.
    pub fn with_data_dir(mut self, dir: String) -> Self {
        self.data_dir = dir;
        self
    }

    /// Create a bot out of the given configuration.
    pub fn build(self) -> telegram::Result<MartiniBot> {
        // Create and test the api.
        let mut api = telegram::Bot::new(self.token);
        let me = try!(api.get_me());

        // Try to open the logfile in writing-append mode if specified
        let file = match self.logfile {
            Some(path) => {
                Some(try!(fs::OpenOptions::new()
                    .write(true)
                    .append(true)
                    .create(true)
                    .open(path)))
            },
            None => None,
        };

        // Check if the data directory exists. Create it otherwise.
        if let Err(e) = fs::create_dir(&Path::new(&*self.data_dir)) {
            if e.kind() != io::ErrorKind::AlreadyExists {
                return Err(e.into());
            }
        }

        Ok(MartiniBot {
            api: Arc::new(Mutex::new(api)),
            me: me,
            flats: FlatMap::new(),
            logger: Logger {
                logfile: file,
                loglevel: self.loglevel,
            },
            data_dir: self.data_dir,
        })
    }
}

impl MartiniBot {
    pub fn from_token(token: String) -> BotBuilder<'static> {
        BotBuilder {
            token: token,
            logfile: None,
            loglevel: LogLevel::Info,
            data_dir: "data/".into(),
        }
    }

    pub fn me(&self) -> telegram::User {
        self.me.clone()
    }

    pub fn log(&mut self, lvl: LogLevel, msg: &str) {
        self.logger.log(lvl, msg);
    }

    fn write_flat(&mut self, cid: telegram::Integer) {
        let fname = format!("{}{}.json", self.data_dir, cid);
        let p = &Path::new(&*fname);

        let file = fs::OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(p);

        let mut file = match file {
            Ok(f) => f,
            Err(e) => {
                log!(self, Error: "Could not open file '{}': {}", fname, e);
                return;
            }
        };

        let res = write!(file, "{}\n", json::as_pretty_json(self.flat(cid)));
        match res {
            Ok(_) =>
                log!(self, Debug: "Wrote file '{}'", fname),
            Err(e) =>
                log!(self, Warning: "Could not write file '{}': {}", fname, e),
        }
    }

    fn flat(&mut self, cid: telegram::Integer) -> &mut FlatShare {
        self.flats.entry(cid).or_insert(FlatShare::default())
    }

    pub fn run(&mut self) {
        // Fetch new updates via long poll method
        let api = self.api.clone();
        let res = api.lock().unwrap().long_poll(None, |api, u| {
            self.handle(api, u)
        });
        if let Err(e) = res {
            log!(self, Error: "An error occured: {}", e);
        }
    }

    fn handle(&mut self,
              api: &mut telegram::Bot,
              u: telegram::Update)
        -> telegram::Result<()>
    {
        use telegram::types::*;

        // If the received update contains a message...
        if let Some(m) = u.message {
            let name = m.from.first_name + &*m.from.last_name
                .map_or("".to_string(), |mut n| { n.insert(0, ' '); n });
            let cid = m.chat.id();

            // if !self.flats.contains_key(&cid) {
            //     self.flats.insert(cid, FlatShare::default());
            // }
            // let flat = self.flats.get_mut(&cid).unwrap();
            // let flat = self.flats.entry(cid);

            // Match message type
            if let MessageType::Text(t) = m.msg {
                // Print received text message to stdout
                log!(self, Debug: "<{}> {}", name, t);

                let command_prefix = Regex::new(r"/\w+ ").unwrap();
                if t.starts_with(&command_prefix) {
                    let arg = t.trim_left_matches(&command_prefix);
                    match t.splitn(2, " ").next().unwrap() {
                        "/need" => {
                            let msg = self.flat(cid).needed.handle_need(arg.into());
                            try!(api.send_message(cid, msg, None, None, None));
                        },
                        "/got" => {
                            let msg = self.flat(cid).needed.handle_got(arg.into());
                            try!(api.send_message(cid, msg, None, None, None));
                        }
                        command => {
                            log!(self, Warning: "Unknown command '{}'", command);
                        }
                    }

                }

                // Update file
                self.write_flat(cid);
            }
        }
        Ok(())
    }

}
<commit_msg>Add deserializing flatshare from file<commit_after>use std::fs;
use std::io;
use std::cmp::{Ordering, PartialOrd};
use std::path::Path;
use needed::Needed;
use regex::Regex;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use telegram;
use std::default::Default;
use std::io::{Read, Write};
use rustc_serialize::json;

#[derive(PartialEq, Eq, Debug)]
pub enum LogLevel {
    Debug,
    Info,
    Warning,
    Error,
}

impl LogLevel {
    pub fn prefix(&self) -> &str {
        use self::LogLevel::*;
        match *self {
            Debug => "DEBUG",
            Info => "INFO ",
            Warning => "WARN ",
            Error => "ERROR",
        }
    }

    fn as_num(&self) -> u8 {
        use self::LogLevel::*;
        match *self {
            Debug   =>  0,
            Info    => 10,
            Warning => 20,
            Error   => 30,
        }
    }
}

impl PartialOrd for LogLevel {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.as_num().partial_cmp(&other.as_num())
    }
}

struct Logger {
    logfile: Option<fs::File>,
    loglevel: LogLevel,
}

impl Logger {
    pub fn log(&mut self, lvl: LogLevel, msg: &str) {
        use term_painter::{Attr, ToStyle};
        use term_painter::Color::*;
        use self::LogLevel::*;

        if lvl < self.loglevel {
            return;
        }

        if let Some(ref mut file) = self.logfile {
            if let Err(e) = write!(file, "[{}] {}\n", lvl.prefix(), msg) {
                panic!("Error occured while writing log file: {}\n{:?}", e, e);
            }
        }

        let prefix = match lvl {
            Error   => Attr::Bold.fg(Red),
            Warning => Attr::Bold.fg(Yellow),
            Info    => Attr::Plain.fg(White),
            Debug   => Attr::Dim.fg(NotSet),
        };
        let text = match lvl {
            Error   => Attr::Bold.fg(Red),
            Warning => Attr::Plain.fg(Yellow),
            Info    => Attr::Plain.fg(NotSet),
            Debug   => Attr::Plain.fg(NotSet),
        };

        println!("[{}] {}", prefix.paint(lvl.prefix()), text.paint(msg));
    }
}

macro_rules! log {
    ($this:ident, $lvl:ident: $fmt:expr) => {
        $this.logger.log(LogLevel::$lvl, $fmt);
    };
    ($this:ident, $lvl:ident: $fmt:expr, $($arg:tt)*) => {
        $this.logger.log(LogLevel::$lvl, &*format!($fmt, $($arg)*));
    };
}

// Data per FlatShare
#[derive(Default, RustcEncodable, RustcDecodable)]
struct FlatShare {
    needed: Needed,
}

// Maps a Telegram 'ChatID' to the corresponding flatshare data
pub type FlatMap = HashMap<telegram::Integer, FlatShare>;

pub struct MartiniBot {
    api: Arc<Mutex<telegram::Bot>>,
    me: telegram::User,
    flats: FlatMap,
    logger: Logger,
    data_dir: String,
}

pub struct BotBuilder<'a> {
    token: String,
    logfile: Option<&'a Path>,
    loglevel: LogLevel,
    data_dir: String,
}

/// Type for building a bot easily
impl<'a> BotBuilder<'a> {
    /// Specifies a logfile to log into. By default the bot does not log
    /// into a file.
    pub fn with_logfile<'b>(self, path: &'b Path) -> BotBuilder<'b> {
        BotBuilder{
            token: self.token,
            logfile: Some(path),
            loglevel: self.loglevel,
            data_dir: self.data_dir,
        }
    }

    /// Specifies the log level of the bot. All messages with level higher or
    /// equal to the specified level will be logged.
    pub fn with_loglevel(mut self, lvl: LogLevel) -> Self {
        self.loglevel = lvl;
        self
    }

    /// Specify the directory where the flatshare data lives.
    pub fn with_data_dir(mut self, dir: String) -> Self {
        self.data_dir = dir;
        self
    }

    /// Create a bot out of the given configuration.
    pub fn build(self) -> telegram::Result<MartiniBot> {
        // Create and test the api.
        let mut api = telegram::Bot::new(self.token);
        let me = try!(api.get_me());

        // Try to open the logfile in writing-append mode if specified
        let file = match self.logfile {
            Some(path) => {
                Some(try!(fs::OpenOptions::new()
                    .write(true)
                    .append(true)
                    .create(true)
                    .open(path)))
            },
            None => None,
        };

        // Check if the data directory exists. Create it otherwise.
        if let Err(e) = fs::create_dir(&Path::new(&*self.data_dir)) {
            if e.kind() != io::ErrorKind::AlreadyExists {
                return Err(e.into());
            }
        }

        Ok(MartiniBot {
            api: Arc::new(Mutex::new(api)),
            me: me,
            flats: FlatMap::new(),
            logger: Logger {
                logfile: file,
                loglevel: self.loglevel,
            },
            data_dir: self.data_dir,
        })
    }
}

impl MartiniBot {
    pub fn from_token(token: String) -> BotBuilder<'static> {
        BotBuilder {
            token: token,
            logfile: None,
            loglevel: LogLevel::Info,
            data_dir: "data/".into(),
        }
    }

    pub fn me(&self) -> telegram::User {
        self.me.clone()
    }

    pub fn log(&mut self, lvl: LogLevel, msg: &str) {
        self.logger.log(lvl, msg);
    }

    fn write_flat(&mut self, cid: telegram::Integer) {
        let fname = format!("{}{}.json", self.data_dir, cid);
        let p = &Path::new(&*fname);

        let file = fs::OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(p);

        let mut file = match file {
            Ok(f) => f,
            Err(e) => {
                log!(self, Error: "Could not open file (w) '{}': {}", fname, e);
                return;
            }
        };

        let res = write!(file, "{}\n", json::as_pretty_json(self.flat(cid)));
        match res {
            Ok(_) =>
                log!(self, Debug: "Wrote file '{}'", fname),
            Err(e) =>
                log!(self, Warning: "Could not write file '{}': {}", fname, e),
        }
    }

    fn read_flat(&mut self, cid: telegram::Integer) -> Option<FlatShare> {
        let fname = format!("{}{}.json", self.data_dir, cid);
        let p = &Path::new(&*fname);

        let file = fs::File::open(p);

        let mut file = match file {
            Ok(f) => f,
            Err(e) => {
                log!(self, Debug: "Could not open file (r) '{}': {}", fname, e);
                return None;
            }
        };

        let mut content = String::new();
        if let Err(e) = file.read_to_string(&mut content) {
            log!(self, Warning: "Could not read file '{}': {}", fname, e);
            return None;
        }

        match json::decode(&*content) {
            Ok(f) => {
                log!(self, Debug: "Read file '{}'", fname);
                Some(f)
            },
            Err(e) => {
                log!(self, Warning: "Could not decode file '{}': {}", fname, e);
                None
            }
        }
    }

    fn flat(&mut self, cid: telegram::Integer) -> &mut FlatShare {
        if !self.flats.contains_key(&cid) {
            let new = self.read_flat(cid).unwrap_or(FlatShare::default());
            self.flats.insert(cid, new);
        }
        self.flats.get_mut(&cid).unwrap()
    }

    pub fn run(&mut self) {
        // Fetch new updates via long poll method
        let api = self.api.clone();
        let res = api.lock().unwrap().long_poll(None, |api, u| {
            self.handle(api, u)
        });
        if let Err(e) = res {
            log!(self, Error: "An error occured: {}", e);
        }
    }

    fn handle(&mut self,
              api: &mut telegram::Bot,
              u: telegram::Update)
        -> telegram::Result<()>
    {
        use telegram::types::*;

        // If the received update contains a message...
        if let Some(m) = u.message {
            let name = m.from.first_name + &*m.from.last_name
                .map_or("".to_string(), |mut n| { n.insert(0, ' '); n });
            let cid = m.chat.id();

            // if !self.flats.contains_key(&cid) {
            //     self.flats.insert(cid, FlatShare::default());
            // }
            // let flat = self.flats.get_mut(&cid).unwrap();
            // let flat = self.flats.entry(cid);

            // Match message type
            if let MessageType::Text(t) = m.msg {
                // Print received text message to stdout
                log!(self, Debug: "<{}> {}", name, t);

                let command_prefix = Regex::new(r"/\w+ ").unwrap();
                if t.starts_with(&command_prefix) {
                    let arg = t.trim_left_matches(&command_prefix);
                    match t.splitn(2, " ").next().unwrap() {
                        "/need" => {
                            let msg = self.flat(cid).needed.handle_need(arg.into());
                            try!(api.send_message(cid, msg, None, None, None));
                        },
                        "/got" => {
                            let msg = self.flat(cid).needed.handle_got(arg.into());
                            try!(api.send_message(cid, msg, None, None, None));
                        }
                        command => {
                            log!(self, Warning: "Unknown command '{}'", command);
                        }
                    }

                }

                // Update file
                self.write_flat(cid);
            }
        }
        Ok(())
    }

}
<|endoftext|>


<commit_before>use clap::{App, ArgMatches};
use std::path::{PathBuf};

use dir;
use error::{ToResult};

extern crate env_logger;

#[derive(Debug)]
pub struct Request {
    pub name: String,
    pub directory: PathBuf,
    pub action: Action,
}

impl Request {
    fn new(name: &str, directory: PathBuf, action: Action) -> Request {
        Request {
            name: String::from(name),
            directory: directory,
            action: action,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Action {
    Get,
    Put,
    List,
    Delete,
    Last,
    ChangeDirectory,
}

pub fn get_request() -> ToResult<Request> {
    let yaml = load_yaml!("cli.yml");
    let app = App::from_yaml(yaml);
    let matches = app.get_matches();

    return get(matches);
}

pub fn get(matches: ArgMatches) -> ToResult<Request> {
    info!("Building CLI request");

    let pathname = match matches.value_of("DIRECTORY") {
        Some(value) => value,
        None => "",
    };

    let directory = try!(dir::resolve(pathname));
    let basename = dir::basename(&directory).expect("TODO: handle this case");
    let name = matches.value_of("NAME").unwrap_or(basename.as_str());
    let request = Request::new(name, directory, Action::ChangeDirectory);

    return Ok(request);
}

#[cfg(test)]
mod tests {
    extern crate env_logger;
    extern crate clap;

    use super::*;
    use std::env;
    use std::path::{PathBuf};

    fn run(mut args: Vec<&str>) -> Request {
        // Logger might fail if it is already initialized.
        // let logger_result = env_logger::init();
        // assert!(logger_result.is_ok());
        let _ = env_logger::init();

        args.insert(0, "to");

        let yaml = load_yaml!("cli.yml");
        let app = clap::App::from_yaml(yaml);
        let matches = app.get_matches_from(args);

        let request = get(matches).expect("should not fail");
        return request;
    }

    #[test]
    fn basic() {
        let result = get_request();
        assert!(result.is_ok());
    }

    #[test]
    fn to_name() {
        let cwd = env::current_dir().expect("should not fail");
        let args = vec!["foo"];
        let request = run(args);

        assert_eq!(request.action, Action::ChangeDirectory);
        assert_eq!(request.name, "foo");
        assert_eq!(request.directory, cwd);
    }
}
<commit_msg>Adds support for cli request actions<commit_after>use clap::{App, ArgMatches};
use std::path::{PathBuf};

use dir;
use error::{ToResult};

extern crate env_logger;

#[derive(Debug)]
pub struct Request {
    pub name: String,
    pub directory: PathBuf,
    pub action: Action,
}

impl Request {
    fn new(name: &str, directory: PathBuf, action: Action) -> Request {
        Request {
            name: String::from(name),
            directory: directory,
            action: action,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Action {
    Get,
    Put,
    List,
    Delete,
    Last,
    ChangeDirectory,
}

impl Action {
    pub fn from(matches: &ArgMatches) -> Action {
        let (get, put, list, delete, last) = (
            matches.is_present("get"),
            matches.is_present("put"),
            matches.is_present("list"),
            matches.is_present("delete"),
            matches.value_of("NAME") == Some("-"),
        );

        let action = match (get, put, list, delete, last) {
            (true, _, _, _, _) => Action::Get,
            (_, true, _, _, _) => Action::Put,
            (_, _, true, _, _) => Action::List,
            (_, _, _, true, _) => Action::Delete,
            (_, _, _, _, true) => Action::Last,
            _ => Action::ChangeDirectory,
        };

        return action;
    }
}

pub fn get_request() -> ToResult<Request> {
    let yaml = load_yaml!("cli.yml");
    let app = App::from_yaml(yaml);
    let matches = app.get_matches();

    return get(matches);
}

pub fn get(matches: ArgMatches) -> ToResult<Request> {
    info!("Building CLI request");

    let pathname = matches.value_of("DIRECTORY").unwrap_or("");
    let directory = try!(dir::resolve(pathname));

    let basename = dir::basename(&directory).expect("TODO: handle this case");
    let name = matches.value_of("NAME").unwrap_or(basename.as_str());

    let action = Action::from(&matches);

    let request = Request::new(name, directory, action);

    return Ok(request);
}

#[cfg(test)]
mod tests {
    extern crate env_logger;
    extern crate clap;

    use super::*;
    use std::env;

    fn run(mut args: Vec<&str>) -> Request {
        // Logger might fail if it is already initialized.
        // let logger_result = env_logger::init();
        // assert!(logger_result.is_ok());
        let _ = env_logger::init();

        args.insert(0, "to");

        let yaml = load_yaml!("cli.yml");
        let app = clap::App::from_yaml(yaml);
        let matches = app.get_matches_from(args);

        let request = get(matches).expect("should not fail");
        return request;
    }

    #[test]
    fn basic() {
        let result = get_request();
        assert!(result.is_ok());
    }

    #[test]
    fn test_name() {
        let cwd = env::current_dir().expect("should not fail");
        let args = vec!["foo"];
        let request = run(args);

        assert_eq!(request.action, Action::ChangeDirectory);
        assert_eq!(request.name, "foo");
        assert_eq!(request.directory, cwd);
    }

    #[test]
    fn no_flags() {
        let request = run(vec![]);
        assert_eq!(request.action, Action::ChangeDirectory);
    }

    #[test]
    fn flag_info() {
        let request = run(vec!["--info"]);
        assert_eq!(request.action, Action::Get);

        let request = run(vec!["-i"]);
        assert_eq!(request.action, Action::Get);
    }

    #[test]
    fn flag_save() {
        let request = run(vec!["--save"]);
        assert_eq!(request.action, Action::Put);

        let request = run(vec!["-s"]);
        assert_eq!(request.action, Action::Put);
    }

    #[test]
    fn flag_list() {
        let request = run(vec!["--list"]);
        assert_eq!(request.action, Action::List);

        let request = run(vec!["-l"]);
        assert_eq!(request.action, Action::List);
    }

    #[test]
    fn flag_delete() {
        let request = run(vec!["--delete"]);
        assert_eq!(request.action, Action::Delete);

        let request = run(vec!["-d"]);
        assert_eq!(request.action, Action::Delete);
    }

    #[test]
    fn last() {
        let request = run(vec!["-"]);
        assert_eq!(request.action, Action::Last);
    }

}
<|endoftext|>


<commit_before>use clap::{App, Arg, ArgGroup, Shell, SubCommand};
use std::io;

const APP_NAME: &str = "git plz";
const CMD_BRANCH: &str = "branch";
const CMD_CHECKOUT: &str = "checkout";
const CMD_COMPLETIONS: &str = "completions";
const CMD_RESET: &str = "reset";
const CMD_STATUS: &str = "status";
const BRANCH: &str = "branch";
const DELETE: &str = "delete";
const FIND: &str = "find";
const SHELL: &str = "shell";

#[derive(Debug)]
pub enum CommandArg {
    Completions { shell: Shell },
    Help,
    Run { option: RunOption },
}

#[derive(Debug)]
pub enum RunOption {
    Branch {
        branch: String,
        option: BranchOption,
    },
    Checkout {
        branch: String,
    },
    Reset,
    Status,
}

#[derive(Debug)]
pub enum BranchOption {
    Delete,
    Find,
}

fn build_cli<'a, 'b>() -> App<'a, 'b> {
    App::new("Git, please")
        .bin_name(APP_NAME)
        .version(crate_version!())
        .author(crate_authors!())
        .about("Run commands on a set of git repositories in a folder tree")
        .subcommand(
            SubCommand::with_name(CMD_BRANCH)
                .about("Perform bulk branch operations")
                .group(ArgGroup::with_name(CMD_BRANCH).required(true))
                .arg(
                    Arg::with_name(DELETE)
                        .short("d")
                        .long("delete")
                        .value_name("BRANCH")
                        .group(CMD_BRANCH),
                )
                .arg(
                    Arg::with_name(FIND)
                        .short("f")
                        .long("find")
                        .value_name("BRANCH")
                        .group(CMD_BRANCH),
                ),
        )
        .subcommand(
            SubCommand::with_name(CMD_CHECKOUT)
                .about("Checkout branch across repos")
                .arg(Arg::with_name(BRANCH).required(true).help("Branch name")),
        )
        .subcommand(
            SubCommand::with_name(CMD_COMPLETIONS)
                .about("Generates completion scripts for your shell")
                .arg(
                    Arg::with_name(SHELL)
                        .required(true)
                        .possible_values(&Shell::variants())
                        .help("The shell to generate the script for"),
                ),
        )
        .subcommand(SubCommand::with_name(CMD_RESET).about("Recursive hard reset"))
        .subcommand(
            SubCommand::with_name(CMD_STATUS)
                .about("Recursive directory search version of git status"),
        )
}

pub fn handle_args() -> CommandArg {
    let matches = build_cli().get_matches();

    match matches.subcommand() {
        (CMD_BRANCH, Some(branch_matches)) => {
            let option = {
                if branch_matches.is_present(DELETE) {
                    BranchOption::Delete
                } else if branch_matches.is_present(FIND) {
                    BranchOption::Find
                } else {
                    panic!("Invalid branch option");
                }
            };

            CommandArg::Run {
                option: RunOption::Branch {
                    branch: value_t!(branch_matches, CMD_BRANCH, String).unwrap(),
                    option,
                },
            }
        }
        (CMD_CHECKOUT, Some(checkout_matches)) => CommandArg::Run {
            option: RunOption::Checkout {
                branch: value_t!(checkout_matches, BRANCH, String).unwrap(),
            },
        },
        (CMD_COMPLETIONS, Some(shell_matches)) => CommandArg::Completions {
            shell: value_t!(shell_matches, SHELL, Shell).unwrap(),
        },
        (CMD_RESET, _) => CommandArg::Run {
            option: RunOption::Reset,
        },
        (CMD_STATUS, _) => CommandArg::Run {
            option: RunOption::Status,
        },

        // By default, show help.
        _ => CommandArg::Help,
    }
}

pub fn gen_completions_for(shell: Shell) {
    build_cli().gen_completions_to(APP_NAME, shell, &mut io::stdout())
}

pub fn print_help() {
    build_cli()
        .print_help()
        .expect("Could not print command line help message");
}
<commit_msg>Better indicate that Branch operates on local branches<commit_after>use clap::{App, Arg, ArgGroup, Shell, SubCommand};
use std::io;

const APP_NAME: &str = "git plz";
const CMD_BRANCH: &str = "branch";
const CMD_CHECKOUT: &str = "checkout";
const CMD_COMPLETIONS: &str = "completions";
const CMD_RESET: &str = "reset";
const CMD_STATUS: &str = "status";
const BRANCH: &str = "branch";
const DELETE: &str = "delete";
const FIND: &str = "find";
const SHELL: &str = "shell";

#[derive(Debug)]
pub enum CommandArg {
    Completions { shell: Shell },
    Help,
    Run { option: RunOption },
}

#[derive(Debug)]
pub enum RunOption {
    Branch {
        branch: String,
        option: BranchOption,
    },
    Checkout {
        branch: String,
    },
    Reset,
    Status,
}

#[derive(Debug)]
pub enum BranchOption {
    Delete,
    Find,
}

fn build_cli<'a, 'b>() -> App<'a, 'b> {
    App::new("Git, please")
        .bin_name(APP_NAME)
        .version(crate_version!())
        .author(crate_authors!())
        .about("Run commands on a set of git repositories in a folder tree")
        .subcommand(
            SubCommand::with_name(CMD_BRANCH)
                .about("Perform bulk local branch operations")
                .group(ArgGroup::with_name(CMD_BRANCH).required(true))
                .arg(
                    Arg::with_name(DELETE)
                        .short("d")
                        .long("delete")
                        .value_name("BRANCH")
                        .group(CMD_BRANCH),
                )
                .arg(
                    Arg::with_name(FIND)
                        .short("f")
                        .long("find")
                        .value_name("BRANCH")
                        .group(CMD_BRANCH),
                ),
        )
        .subcommand(
            SubCommand::with_name(CMD_CHECKOUT)
                .about("Checkout branch across repos")
                .arg(Arg::with_name(BRANCH).required(true).help("Branch name")),
        )
        .subcommand(
            SubCommand::with_name(CMD_COMPLETIONS)
                .about("Generates completion scripts for your shell")
                .arg(
                    Arg::with_name(SHELL)
                        .required(true)
                        .possible_values(&Shell::variants())
                        .help("The shell to generate the script for"),
                ),
        )
        .subcommand(SubCommand::with_name(CMD_RESET).about("Recursive hard reset"))
        .subcommand(
            SubCommand::with_name(CMD_STATUS)
                .about("Recursive directory search version of git status"),
        )
}

pub fn handle_args() -> CommandArg {
    let matches = build_cli().get_matches();

    match matches.subcommand() {
        (CMD_BRANCH, Some(branch_matches)) => {
            let option = {
                if branch_matches.is_present(DELETE) {
                    BranchOption::Delete
                } else if branch_matches.is_present(FIND) {
                    BranchOption::Find
                } else {
                    panic!("Invalid branch option");
                }
            };

            CommandArg::Run {
                option: RunOption::Branch {
                    branch: value_t!(branch_matches, CMD_BRANCH, String).unwrap(),
                    option,
                },
            }
        }
        (CMD_CHECKOUT, Some(checkout_matches)) => CommandArg::Run {
            option: RunOption::Checkout {
                branch: value_t!(checkout_matches, BRANCH, String).unwrap(),
            },
        },
        (CMD_COMPLETIONS, Some(shell_matches)) => CommandArg::Completions {
            shell: value_t!(shell_matches, SHELL, Shell).unwrap(),
        },
        (CMD_RESET, _) => CommandArg::Run {
            option: RunOption::Reset,
        },
        (CMD_STATUS, _) => CommandArg::Run {
            option: RunOption::Status,
        },

        // By default, show help.
        _ => CommandArg::Help,
    }
}

pub fn gen_completions_for(shell: Shell) {
    build_cli().gen_completions_to(APP_NAME, shell, &mut io::stdout())
}

pub fn print_help() {
    build_cli()
        .print_help()
        .expect("Could not print command line help message");
}
<|endoftext|>


<commit_before>//! This exposes the command line interface that the binary uses
use std::fs;
use std::io;
use std::env;
use std::process;
use std::sync::Mutex;

use clap::{App, Arg, SubCommand, ArgMatches, AppSettings};
use chrono;
use log;
use openssl_probe::init_ssl_cert_env_vars;

use super::{Result, ResultExt, Error};
use super::sdk::{Sdk, SdkInfo, DumpOptions};
use super::config::Config;
use super::constants::VERSION;
use super::memdb::stash::{MemDbStash, SyncOptions};
use super::api::server::{ApiServer, BindOptions};

struct SimpleLogger<W: ?Sized> {
    f: Mutex<Box<W>>,
}

impl<W: io::Write + Send + ?Sized> log::Log for SimpleLogger<W> {

    fn enabled(&self, metadata: &log::LogMetadata) -> bool {
        metadata.level() <= log::LogLevel::Info
    }

    fn log(&self, record: &log::LogRecord) {
        let mut f = self.f.lock().unwrap();
        if self.enabled(record.metadata()) {
            writeln!(f, "[{}] {} | {}{}",
                     chrono::Local::now(),
                     record.target().split(':').next().unwrap(),
                     match record.level() {
                         log::LogLevel::Error => "ERROR: ",
                         log::LogLevel::Warn => "WARNING: ",
                         _ => "",
                     },
                     record.args()).ok();
        }
    }
}

fn setup_openssl() {
    init_ssl_cert_env_vars();
}

fn setup_logging(config: &Config) -> Result<()> {
    let filter = config.get_log_level_filter()?;
    if filter >= log::LogLevel::Debug {
        env::set_var("RUST_BACKTRACE", "1");
    }

    let f : Box<io::Write + Send> = match config.get_log_filename()? {
        Some(path) => Box::new(fs::File::open(path)?),
        None => Box::new(io::stdout()),
    };
    log::set_logger(|max_log_level| {
        max_log_level.set(filter);
        Box::new(SimpleLogger {
            f: Mutex::new(f),
        })
    }).unwrap();
    Ok(())
}

/// Main entry point that starts the CLI
pub fn main() {
    match execute() {
        Ok(()) => {},
        Err(err) => {
            use std::error::Error;
            println!("error: {}", err);
            let mut cause = err.cause();
            while let Some(the_cause) = cause {
                println!("  caused by: {}", the_cause);
                cause = the_cause.cause();
            }
            if let Some(backtrace) = err.backtrace() {
                debug!("  Traceback: {:?}", backtrace);
            }
            process::exit(1);
        }
    }
}

fn config_from_matches(matches: &ArgMatches) -> Result<Config> {
    let mut cfg = if let Some(config_path) = matches.value_of("config") {
        Config::load_file(config_path)?
    } else {
        Config::load_default()?
    };

    if let Some(value) = matches.value_of("log_level") {
        let filter = value.parse()
            .map_err(|_| Error::from("Invalid value for log level"))?;
        cfg.set_log_level_filter(filter);
    };

    if let Some(value) = matches.value_of("symbol_dir") {
        cfg.set_symbol_dir(value);
    }

    if let Some(value) = matches.value_of("aws_bucket_url") {
        cfg.set_aws_bucket_url(value);
    }

    if let Some(value) = matches.value_of("aws_region") {
        let region = value.parse()
            .map_err(|_| Error::from("Invalid AWS region"))?;
        cfg.set_aws_region(region);
    }

    Ok(cfg)
}

fn execute() -> Result<()> {
    setup_openssl();

    let app = App::new("sentry-symbolserver")
        .version(VERSION)
        .about("This tool implements an Apple SDK processor and server.")
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .setting(AppSettings::ColorNever)
        .arg(Arg::with_name("config")
             .long("config")
             .value_name("FILE")
             .help("The path to the config file"))
        .arg(Arg::with_name("log_level")
             .short("l")
             .long("log-level")
             .value_name("LEVEL")
             .help("Overrides the log level from the config"))
        .arg(Arg::with_name("symbol_dir")
             .short("p")
             .long("symbol-dir")
             .value_name("PATH")
             .help("The path to the symbol directory"))
        .arg(Arg::with_name("aws_bucket_url")
             .long("aws-bucket-url")
             .value_name("URL")
             .help("The bucket URL the sync tool should pull from"))
        .arg(Arg::with_name("aws_region")
             .long("aws-region")
             .value_name("REGION")
             .help("Sets the AWS region the bucket is located in"))
        .subcommand(
            SubCommand::with_name("sync")
                .about("Updates symbols from S3"))
        .subcommand(
            SubCommand::with_name("run")
                .about("Runs the symbol server")
                .arg(Arg::with_name("disable_sync")
                     .long("disable-sync")
                     .help("Disables the background synching"))
                .arg(Arg::with_name("bind")
                     .long("bind")
                     .value_name("ADDR")
                     .help("Bind to a specific address (ip:port)"))
                .arg(Arg::with_name("bind_fd")
                     .long("bind-fd")
                     .value_name("FD")
                     .help("Bind to a specific file descriptor"))
                .arg(Arg::with_name("threads")
                     .long("threads")
                     .short("t")
                     .value_name("COUNT")
                     .help("Overrides the default listener thread count")))
        .subcommand(
            SubCommand::with_name("convert-sdk")
                .about("Converts an SDK into a memdb file")
                .arg(Arg::with_name("path")
                     .index(1)
                     .value_name("PATH")
                     .required(true)
                     .multiple(true)
                     .help("Path to the support folder"))
                .arg(Arg::with_name("compress")
                     .short("c")
                     .long("compress")
                     .help("Write compressed files instead."))
                .arg(Arg::with_name("output-path")
                     .short("o")
                     .long("output")
                     .help("Where the result should be stored")))
        .subcommand(
            SubCommand::with_name("dump-object")
                .about("Dumps an object from a memdb in the stash")
                .arg(Arg::with_name("sdk_id")
                     .index(1)
                     .value_name("SDK_ID")
                     .required(true)
                     .help("The SDK id to dump"))
                .arg(Arg::with_name("name_or_uuid")
                     .index(2)
                     .value_name("NAME_OR_UUID")
                     .required(true)
                     .help("The object to dump")))
        .subcommand(
            SubCommand::with_name("sdk-fuzzy-match")
                .about("Given an SDK ID finds the fuzzy matches in order of quality")
                .arg(Arg::with_name("sdk_id")
                     .index(1)
                     .value_name("SDK_ID")
                     .required(true)
                     .help("The SDK id to dump")));
    let matches = app.get_matches();

    let cfg = config_from_matches(&matches)?;
    setup_logging(&cfg)?;

    if let Some(matches) = matches.subcommand_matches("convert-sdk") {
        convert_sdk_action(matches.values_of("path").unwrap().collect(),
                           matches.value_of("output-path").unwrap_or("."),
                           matches.is_present("compress"))?;
    } else if let Some(matches) = matches.subcommand_matches("dump-object") {
        dump_object_action(&cfg, matches.value_of("sdk_id").unwrap(),
                           matches.value_of("name_or_uuid").unwrap())?;
    } else if let Some(matches) = matches.subcommand_matches("sdk-fuzzy-match") {
        sdk_fuzzy_match_action(&cfg, matches.value_of("sdk_id").unwrap())?;
    } else if let Some(matches) = matches.subcommand_matches("run") {
        run_action(&cfg, matches)?;
    } else if let Some(_matches) = matches.subcommand_matches("sync") {
        sync_action(&cfg)?;
    }

    Ok(())
}

fn convert_sdk_action(paths: Vec<&str>, output_path: &str, compress: bool)
    -> Result<()>
{
    let dst_base = env::current_dir().unwrap().join(output_path);

    for (idx, path) in paths.iter().enumerate() {
        if idx > 0 {
            println!("");
        }
        let sdk = Sdk::new(&path)?;
        let mut dst = dst_base.join(sdk.info().memdb_filename());
        if compress {
            dst.set_extension("memdbz");
        }

        println!("SDK {} ({} {}):", sdk.info().name(),
                 sdk.info().version(), sdk.info().build().unwrap_or("UNKNONW"));

        // make sure we close the file at the end, in case we want to
        // re-open it for compressing.
        let f = fs::File::create(&dst)?;
        let options = DumpOptions {
            show_progress_bar: true,
            compress: compress,
            ..Default::default()
        };
        sdk.dump_memdb(f, options)?;
    }

    Ok(())
}

fn dump_object_action(config: &Config, sdk_id: &str, name_or_uuid: &str) -> Result<()> {
    let stash = MemDbStash::new(config)?;
    let info = SdkInfo::from_filename(sdk_id).ok_or_else(||
        Error::from("Invalid SDK ID"))?;
    let memdb = stash.get_memdb(&info)?;
    let uuid = memdb.find_uuid_fuzzy(name_or_uuid)?.ok_or_else(||
        Error::from("Object not found in SDK"))?;

    for item_rv in memdb.iter_symbols(uuid)? {
        let item = item_rv?;
        println!("{:>014x} {}", item.addr(), item.symbol());
    }
    Ok(())
}

fn sdk_fuzzy_match_action(config: &Config, sdk_id: &str) -> Result<()> {
    let stash = MemDbStash::new(config)?;
    let sdk_infos = stash.fuzzy_match_sdk_id(sdk_id)?;
    for info in sdk_infos {
        println!("> {}", info.sdk_id());
    }
    Ok(())
}

fn sync_action(config: &Config) -> Result<()> {
    let stash = MemDbStash::new(config)?;
    stash.sync(SyncOptions {
        user_facing: true,
        ..Default::default()
    })?;
    Ok(())
}

fn run_action(config: &Config, matches: &ArgMatches) -> Result<()> {
    let api_server = ApiServer::new(config, !matches.is_present("disable_sync"))?;

    let threads: usize = if let Some(threads) = matches.value_of("threads") {
        threads.parse().chain_err(|| "invalid value for threads")?
    } else {
        config.get_server_threads()?
    };

    api_server.run(threads, if let Some(addr) = matches.value_of("bind") {
        BindOptions::BindToAddr(addr)
    } else if let Some(fd) = matches.value_of("bind_fd") {
        BindOptions::BindToFd(fd.parse().chain_err(|| "invalid value for file descriptor")?)
    } else {
        BindOptions::UseConfig
    })?;

    Ok(())
}
<commit_msg>Added --default-location command<commit_after>//! This exposes the command line interface that the binary uses
use std::fs;
use std::io;
use std::env;
use std::process;
use std::path::PathBuf;
use std::sync::Mutex;

use clap::{App, Arg, SubCommand, ArgMatches, AppSettings};
use chrono;
use log;
use openssl_probe::init_ssl_cert_env_vars;

use super::{Result, ResultExt, Error};
use super::sdk::{Sdk, SdkInfo, DumpOptions};
use super::config::Config;
use super::constants::VERSION;
use super::memdb::stash::{MemDbStash, SyncOptions};
use super::api::server::{ApiServer, BindOptions};

struct SimpleLogger<W: ?Sized> {
    f: Mutex<Box<W>>,
}

impl<W: io::Write + Send + ?Sized> log::Log for SimpleLogger<W> {

    fn enabled(&self, metadata: &log::LogMetadata) -> bool {
        metadata.level() <= log::LogLevel::Info
    }

    fn log(&self, record: &log::LogRecord) {
        let mut f = self.f.lock().unwrap();
        if self.enabled(record.metadata()) {
            writeln!(f, "[{}] {} | {}{}",
                     chrono::Local::now(),
                     record.target().split(':').next().unwrap(),
                     match record.level() {
                         log::LogLevel::Error => "ERROR: ",
                         log::LogLevel::Warn => "WARNING: ",
                         _ => "",
                     },
                     record.args()).ok();
        }
    }
}

fn setup_openssl() {
    init_ssl_cert_env_vars();
}

fn setup_logging(config: &Config) -> Result<()> {
    let filter = config.get_log_level_filter()?;
    if filter >= log::LogLevel::Debug {
        env::set_var("RUST_BACKTRACE", "1");
    }

    let f : Box<io::Write + Send> = match config.get_log_filename()? {
        Some(path) => Box::new(fs::File::open(path)?),
        None => Box::new(io::stdout()),
    };
    log::set_logger(|max_log_level| {
        max_log_level.set(filter);
        Box::new(SimpleLogger {
            f: Mutex::new(f),
        })
    }).unwrap();
    Ok(())
}

/// Main entry point that starts the CLI
pub fn main() {
    match execute() {
        Ok(()) => {},
        Err(err) => {
            use std::error::Error;
            println!("error: {}", err);
            let mut cause = err.cause();
            while let Some(the_cause) = cause {
                println!("  caused by: {}", the_cause);
                cause = the_cause.cause();
            }
            if let Some(backtrace) = err.backtrace() {
                debug!("  Traceback: {:?}", backtrace);
            }
            process::exit(1);
        }
    }
}

fn config_from_matches(matches: &ArgMatches) -> Result<Config> {
    let mut cfg = if let Some(config_path) = matches.value_of("config") {
        Config::load_file(config_path)?
    } else {
        Config::load_default()?
    };

    if let Some(value) = matches.value_of("log_level") {
        let filter = value.parse()
            .map_err(|_| Error::from("Invalid value for log level"))?;
        cfg.set_log_level_filter(filter);
    };

    if let Some(value) = matches.value_of("symbol_dir") {
        cfg.set_symbol_dir(value);
    }

    if let Some(value) = matches.value_of("aws_bucket_url") {
        cfg.set_aws_bucket_url(value);
    }

    if let Some(value) = matches.value_of("aws_region") {
        let region = value.parse()
            .map_err(|_| Error::from("Invalid AWS region"))?;
        cfg.set_aws_region(region);
    }

    Ok(cfg)
}

fn get_default_sdks() -> Result<Vec<PathBuf>> {
    let mut rv = vec![];
    let path = env::home_dir().ok_or_else(|| {
        Error::from("Could not find home folder")
    })?.join("Library/Developer/Xcode/iOS DeviceSupport");
    for entry_rv in fs::read_dir(path).chain_err(
        || Error::from("Could not find iOS DeviceSupport"))?
    {
        let entry = entry_rv?;
        let md = entry.metadata()?;
        if_chain! {
            if md.is_dir();
            if let Ok(plist_md) = entry.path().join("Info.plist").metadata();
            if plist_md.is_file();
            then {
                rv.push(entry.path().to_path_buf());
            }
        }
    }
    Ok(rv)
}

fn execute() -> Result<()> {
    setup_openssl();

    let app = App::new("sentry-symbolserver")
        .version(VERSION)
        .about("This tool implements an Apple SDK processor and server.")
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .setting(AppSettings::ColorNever)
        .arg(Arg::with_name("config")
             .long("config")
             .value_name("FILE")
             .help("The path to the config file"))
        .arg(Arg::with_name("log_level")
             .short("l")
             .long("log-level")
             .value_name("LEVEL")
             .help("Overrides the log level from the config"))
        .arg(Arg::with_name("symbol_dir")
             .short("p")
             .long("symbol-dir")
             .value_name("PATH")
             .help("The path to the symbol directory"))
        .arg(Arg::with_name("aws_bucket_url")
             .long("aws-bucket-url")
             .value_name("URL")
             .help("The bucket URL the sync tool should pull from"))
        .arg(Arg::with_name("aws_region")
             .long("aws-region")
             .value_name("REGION")
             .help("Sets the AWS region the bucket is located in"))
        .subcommand(
            SubCommand::with_name("sync")
                .about("Updates symbols from S3"))
        .subcommand(
            SubCommand::with_name("run")
                .about("Runs the symbol server")
                .arg(Arg::with_name("disable_sync")
                     .long("disable-sync")
                     .help("Disables the background synching"))
                .arg(Arg::with_name("bind")
                     .long("bind")
                     .value_name("ADDR")
                     .help("Bind to a specific address (ip:port)"))
                .arg(Arg::with_name("bind_fd")
                     .long("bind-fd")
                     .value_name("FD")
                     .help("Bind to a specific file descriptor"))
                .arg(Arg::with_name("threads")
                     .long("threads")
                     .short("t")
                     .value_name("COUNT")
                     .help("Overrides the default listener thread count")))
        .subcommand(
            SubCommand::with_name("convert-sdk")
                .about("Converts an SDK into a memdb file")
                .arg(Arg::with_name("default_location")
                     .long("default-location")
                     .help("Find SDKs at the default location"))
                .arg(Arg::with_name("path")
                     .index(1)
                     .value_name("PATH")
                     .multiple(true)
                     .help("Path to the support folder"))
                .arg(Arg::with_name("compress")
                     .short("c")
                     .long("compress")
                     .help("Write compressed files instead."))
                .arg(Arg::with_name("output-path")
                     .short("o")
                     .long("output")
                     .help("Where the result should be stored")))
        .subcommand(
            SubCommand::with_name("dump-object")
                .about("Dumps an object from a memdb in the stash")
                .arg(Arg::with_name("sdk_id")
                     .index(1)
                     .value_name("SDK_ID")
                     .required(true)
                     .help("The SDK id to dump"))
                .arg(Arg::with_name("name_or_uuid")
                     .index(2)
                     .value_name("NAME_OR_UUID")
                     .required(true)
                     .help("The object to dump")))
        .subcommand(
            SubCommand::with_name("sdk-fuzzy-match")
                .about("Given an SDK ID finds the fuzzy matches in order of quality")
                .arg(Arg::with_name("sdk_id")
                     .index(1)
                     .value_name("SDK_ID")
                     .required(true)
                     .help("The SDK id to dump")));
    let matches = app.get_matches();

    let cfg = config_from_matches(&matches)?;
    setup_logging(&cfg)?;

    if let Some(matches) = matches.subcommand_matches("convert-sdk") {
        let paths = if matches.is_present("default_location") {
            get_default_sdks()?
        } else if let Some(paths) = matches.values_of("path") {
            paths.map(|x| PathBuf::from(x)).collect()
        } else {
            return Err(Error::from("No paths provided"));
        };
        convert_sdk_action(paths,
                           matches.value_of("output-path").unwrap_or("."),
                           matches.is_present("compress"))?;
    } else if let Some(matches) = matches.subcommand_matches("dump-object") {
        dump_object_action(&cfg, matches.value_of("sdk_id").unwrap(),
                           matches.value_of("name_or_uuid").unwrap())?;
    } else if let Some(matches) = matches.subcommand_matches("sdk-fuzzy-match") {
        sdk_fuzzy_match_action(&cfg, matches.value_of("sdk_id").unwrap())?;
    } else if let Some(matches) = matches.subcommand_matches("run") {
        run_action(&cfg, matches)?;
    } else if let Some(_matches) = matches.subcommand_matches("sync") {
        sync_action(&cfg)?;
    }

    Ok(())
}

fn convert_sdk_action(paths: Vec<PathBuf>, output_path: &str, compress: bool)
    -> Result<()>
{
    let dst_base = env::current_dir().unwrap().join(output_path);

    for (idx, path) in paths.iter().enumerate() {
        if idx > 0 {
            println!("");
        }
        let sdk = Sdk::new(&path)?;
        let mut dst = dst_base.join(sdk.info().memdb_filename());
        if compress {
            dst.set_extension("memdbz");
        }

        println!("SDK {} ({} {}):", sdk.info().name(),
                 sdk.info().version(), sdk.info().build().unwrap_or("UNKNONW"));

        // make sure we close the file at the end, in case we want to
        // re-open it for compressing.
        let f = fs::File::create(&dst)?;
        let options = DumpOptions {
            show_progress_bar: true,
            compress: compress,
            ..Default::default()
        };
        sdk.dump_memdb(f, options)?;
    }

    Ok(())
}

fn dump_object_action(config: &Config, sdk_id: &str, name_or_uuid: &str) -> Result<()> {
    let stash = MemDbStash::new(config)?;
    let info = SdkInfo::from_filename(sdk_id).ok_or_else(||
        Error::from("Invalid SDK ID"))?;
    let memdb = stash.get_memdb(&info)?;
    let uuid = memdb.find_uuid_fuzzy(name_or_uuid)?.ok_or_else(||
        Error::from("Object not found in SDK"))?;

    for item_rv in memdb.iter_symbols(uuid)? {
        let item = item_rv?;
        println!("{:>014x} {}", item.addr(), item.symbol());
    }
    Ok(())
}

fn sdk_fuzzy_match_action(config: &Config, sdk_id: &str) -> Result<()> {
    let stash = MemDbStash::new(config)?;
    let sdk_infos = stash.fuzzy_match_sdk_id(sdk_id)?;
    for info in sdk_infos {
        println!("> {}", info.sdk_id());
    }
    Ok(())
}

fn sync_action(config: &Config) -> Result<()> {
    let stash = MemDbStash::new(config)?;
    stash.sync(SyncOptions {
        user_facing: true,
        ..Default::default()
    })?;
    Ok(())
}

fn run_action(config: &Config, matches: &ArgMatches) -> Result<()> {
    let api_server = ApiServer::new(config, !matches.is_present("disable_sync"))?;

    let threads: usize = if let Some(threads) = matches.value_of("threads") {
        threads.parse().chain_err(|| "invalid value for threads")?
    } else {
        config.get_server_threads()?
    };

    api_server.run(threads, if let Some(addr) = matches.value_of("bind") {
        BindOptions::BindToAddr(addr)
    } else if let Some(fd) = matches.value_of("bind_fd") {
        BindOptions::BindToFd(fd.parse().chain_err(|| "invalid value for file descriptor")?)
    } else {
        BindOptions::UseConfig
    })?;

    Ok(())
}
<|endoftext|>


<commit_before>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
extern crate log;
use clap::{Arg, App};
use index::{Line, Column};
use config::{Dimensions, Shell};
use std::path::{Path, PathBuf};
use std::borrow::Cow;

const DEFAULT_TITLE: &'static str = "Alacritty";

/// Options specified on the command line
pub struct Options {
    pub live_config_reload: Option<bool>,
    pub print_events: bool,
    pub ref_test: bool,
    pub dimensions: Option<Dimensions>,
    pub title: String,
    pub log_level: log::LogLevelFilter,
    pub command: Option<Shell<'static>>,
    pub working_dir: Option<PathBuf>,
    pub config: Option<PathBuf>,
}

impl Default for Options {
    fn default() -> Options {
        Options {
            live_config_reload: None,
            print_events: false,
            ref_test: false,
            dimensions: None,
            title: DEFAULT_TITLE.to_owned(),
            log_level: log::LogLevelFilter::Warn,
            command: None,
            working_dir: None,
            config: None,
        }
    }
}

impl Options {
    /// Build `Options` from command line arguments
    pub fn load() -> Options {
        let mut options = Options::default();

        let matches = App::new(crate_name!())
            .version(crate_version!())
            .author(crate_authors!("\n"))
            .about(crate_description!())
            .arg(Arg::with_name("ref-test")
                .long("ref-test")
                .help("Generates ref test"))
            .arg(Arg::with_name("live-config-reload")
                .long("live-config-reload")
                .help("Enable automatic config reloading"))
            .arg(Arg::with_name("no-live-config-reload")
                 .long("no-live-config-reload")
                 .help("Disable automatic config reloading")
                 .conflicts_with("live-config-reload"))
            .arg(Arg::with_name("print-events")
                .long("print-events"))
            .arg(Arg::with_name("dimensions")
                .long("dimensions")
                .short("d")
                .value_names(&["columns", "lines"])
                 .help("Defines the window dimensions. Falls back to size specified by window manager if set to 0x0 [default: 80x24]"))
            .arg(Arg::with_name("title")
                .long("title")
                .short("t")
                .default_value(DEFAULT_TITLE)
                .help("Defines the window title"))
            .arg(Arg::with_name("q")
                .short("q")
                .multiple(true)
                .conflicts_with("v")
                .help("Reduces the level of verbosity (the min level is -qq)"))
            .arg(Arg::with_name("v")
                .short("v")
                .multiple(true)
                .conflicts_with("q")
                .help("Increases the level of verbosity (the max level is -vvv)"))
            .arg(Arg::with_name("working-directory")
                 .long("working-directory")
                 .takes_value(true)
                 .help("Start the shell in the specified working directory"))
            .arg(Arg::with_name("config-file")
                 .long("config-file")
                 .takes_value(true)
                 .help("Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]"))
            .arg(Arg::with_name("command")
                .long("command")
                .short("e")
                .multiple(true)
                .takes_value(true)
                .min_values(1)
                .allow_hyphen_values(true)
                .help("Command and args to execute (must be last argument)"))
            .get_matches();

        if matches.is_present("ref-test") {
            options.ref_test = true;
        }

        if matches.is_present("print-events") {
            options.print_events = true;
        }

        if matches.is_present("live-config-reload") {
            options.live_config_reload = Some(true);
        } else if matches.is_present("no-live-config-reload") {
            options.live_config_reload = Some(false);
        }

        if let Some(mut dimensions) = matches.values_of("dimensions") {
            let width = dimensions.next().map(|w| w.parse().map(Column));
            let height = dimensions.next().map(|h| h.parse().map(Line));
            if let (Some(Ok(width)), Some(Ok(height))) = (width, height) {
                options.dimensions = Some(Dimensions::new(width, height));
            }
        }

        if let Some(title) = matches.value_of("title") {
            options.title = title.to_owned();
        }

        match matches.occurrences_of("q") {
            0 => {},
            1 => options.log_level = log::LogLevelFilter::Error,
            2 | _ => options.log_level = log::LogLevelFilter::Off
        }

        match matches.occurrences_of("v") {
            0 => {},
            1 => options.log_level = log::LogLevelFilter::Info,
            2 => options.log_level = log::LogLevelFilter::Debug,
            3 | _ => options.log_level = log::LogLevelFilter::Trace
        }

        if let Some(dir) = matches.value_of("working-directory") {
            options.working_dir = Some(PathBuf::from(dir.to_string()));
        }

        if let Some(path) = matches.value_of("config-file") {
            options.config = Some(PathBuf::from(path.to_string()));
        }

        if let Some(mut args) = matches.values_of("command") {
            // The following unwrap is guaranteed to succeed.
            // If 'command' exists it must also have a first item since
            // Arg::min_values(1) is set.
            let command = String::from(args.next().unwrap());
            let args = args.map(String::from).collect();
            options.command = Some(Shell::new_with_args(command, args));
        }

        options
    }

    pub fn dimensions(&self) -> Option<Dimensions> {
        self.dimensions
    }

    pub fn command(&self) -> Option<&Shell> {
        self.command.as_ref()
    }

    pub fn config_path(&self) -> Option<Cow<Path>> {
        self.config.as_ref().map(|p| Cow::Borrowed(p.as_path()))
    }
}
<commit_msg>Style fixes<commit_after>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
extern crate log;
use clap::{Arg, App};
use index::{Line, Column};
use config::{Dimensions, Shell};
use std::path::{Path, PathBuf};
use std::borrow::Cow;

const DEFAULT_TITLE: &'static str = "Alacritty";

/// Options specified on the command line
pub struct Options {
    pub live_config_reload: Option<bool>,
    pub print_events: bool,
    pub ref_test: bool,
    pub dimensions: Option<Dimensions>,
    pub title: String,
    pub log_level: log::LogLevelFilter,
    pub command: Option<Shell<'static>>,
    pub working_dir: Option<PathBuf>,
    pub config: Option<PathBuf>,
}

impl Default for Options {
    fn default() -> Options {
        Options {
            live_config_reload: None,
            print_events: false,
            ref_test: false,
            dimensions: None,
            title: DEFAULT_TITLE.to_owned(),
            log_level: log::LogLevelFilter::Warn,
            command: None,
            working_dir: None,
            config: None,
        }
    }
}

impl Options {
    /// Build `Options` from command line arguments
    pub fn load() -> Options {
        let mut options = Options::default();

        let matches = App::new(crate_name!())
            .version(crate_version!())
            .author(crate_authors!("\n"))
            .about(crate_description!())
            .arg(Arg::with_name("ref-test")
                .long("ref-test")
                .help("Generates ref test"))
            .arg(Arg::with_name("live-config-reload")
                .long("live-config-reload")
                .help("Enable automatic config reloading"))
            .arg(Arg::with_name("no-live-config-reload")
                 .long("no-live-config-reload")
                 .help("Disable automatic config reloading")
                 .conflicts_with("live-config-reload"))
            .arg(Arg::with_name("print-events")
                .long("print-events"))
            .arg(Arg::with_name("dimensions")
                .long("dimensions")
                .short("d")
                .value_names(&["columns", "lines"])
                .help("Defines the window dimensions. Falls back to size specified by \
                       window manager if set to 0x0 [default: 80x24]"))
            .arg(Arg::with_name("title")
                .long("title")
                .short("t")
                .default_value(DEFAULT_TITLE)
                .help("Defines the window title"))
            .arg(Arg::with_name("q")
                .short("q")
                .multiple(true)
                .conflicts_with("v")
                .help("Reduces the level of verbosity (the min level is -qq)"))
            .arg(Arg::with_name("v")
                .short("v")
                .multiple(true)
                .conflicts_with("q")
                .help("Increases the level of verbosity (the max level is -vvv)"))
            .arg(Arg::with_name("working-directory")
                 .long("working-directory")
                 .takes_value(true)
                 .help("Start the shell in the specified working directory"))
            .arg(Arg::with_name("config-file")
                 .long("config-file")
                 .takes_value(true)
                 .help("Specify alternative configuration file \
                       [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]"))
            .arg(Arg::with_name("command")
                .long("command")
                .short("e")
                .multiple(true)
                .takes_value(true)
                .min_values(1)
                .allow_hyphen_values(true)
                .help("Command and args to execute (must be last argument)"))
            .get_matches();

        if matches.is_present("ref-test") {
            options.ref_test = true;
        }

        if matches.is_present("print-events") {
            options.print_events = true;
        }

        if matches.is_present("live-config-reload") {
            options.live_config_reload = Some(true);
        } else if matches.is_present("no-live-config-reload") {
            options.live_config_reload = Some(false);
        }

        if let Some(mut dimensions) = matches.values_of("dimensions") {
            let width = dimensions.next().map(|w| w.parse().map(Column));
            let height = dimensions.next().map(|h| h.parse().map(Line));
            if let (Some(Ok(width)), Some(Ok(height))) = (width, height) {
                options.dimensions = Some(Dimensions::new(width, height));
            }
        }

        if let Some(title) = matches.value_of("title") {
            options.title = title.to_owned();
        }

        match matches.occurrences_of("q") {
            0 => {},
            1 => options.log_level = log::LogLevelFilter::Error,
            2 | _ => options.log_level = log::LogLevelFilter::Off
        }

        match matches.occurrences_of("v") {
            0 => {},
            1 => options.log_level = log::LogLevelFilter::Info,
            2 => options.log_level = log::LogLevelFilter::Debug,
            3 | _ => options.log_level = log::LogLevelFilter::Trace
        }

        if let Some(dir) = matches.value_of("working-directory") {
            options.working_dir = Some(PathBuf::from(dir.to_string()));
        }

        if let Some(path) = matches.value_of("config-file") {
            options.config = Some(PathBuf::from(path.to_string()));
        }

        if let Some(mut args) = matches.values_of("command") {
            // The following unwrap is guaranteed to succeed.
            // If 'command' exists it must also have a first item since
            // Arg::min_values(1) is set.
            let command = String::from(args.next().unwrap());
            let args = args.map(String::from).collect();
            options.command = Some(Shell::new_with_args(command, args));
        }

        options
    }

    pub fn dimensions(&self) -> Option<Dimensions> {
        self.dimensions
    }

    pub fn command(&self) -> Option<&Shell> {
        self.command.as_ref()
    }

    pub fn config_path(&self) -> Option<Cow<Path>> {
        self.config.as_ref().map(|p| Cow::Borrowed(p.as_path()))
    }
}
<|endoftext|>