<commit_before>use ast;
use ast::Ast;
use ast::Function;
use ast::Elem::ElemFunction;

use error::ErrorCode;
use error::ParseError;

use parser::Parser;

use sym;
use sym::SymTable;
use sym::Sym::SymFunction;
use sym::SymFunctionType;

macro_rules! err {
    ( $errs: expr, $x: expr ) => {
        {
            match $x {
                Ok(val) => val,
                Err(_) => { return Err($errs); }
            }
        }
    };
}

// Only do semantic analysis until some amount of
// errors found
static MAX_ERRORS: usize = 5;

pub struct SemCheck<'a> {
    ast: &'a Ast,
    errors: Vec<ParseError>,
}

impl<'a> SemCheck<'a> {
    pub fn new(ast: &Ast) -> SemCheck {
        SemCheck {
            ast: ast,
            errors: Vec::new(),
        }
    }

    pub fn check(mut self) -> Result<SymTable, Vec<ParseError>> {
        let mut globals = SymTable::new();

        err!(self.errors, self.parse_function_headers(&mut globals));
        err!(self.errors, self.parse_function_bodies(&mut globals));

        if self.errors.len() == 0 {
            Ok(globals)
        } else {
            Err(self.errors)
        }
    }

    fn parse_function_headers(&mut self, globals: &mut SymTable) -> Result<(), ()> {
        for elem in &self.ast.elements {
            match *elem {
                ElemFunction(ref fct) => {
                    try!(self.add_function_header(globals, fct));
                }

                _ => unreachable!()
            }
        }

        Ok(())
    }

    fn parse_function_bodies(&mut self, globals: &mut SymTable) -> Result<(), ()> {
        for fct in globals.functions_mut() {
            try!(self.check_function_body(fct));
        }

        Ok(())
    }

    fn add_function_header(&mut self, globals: &mut SymTable, fct: &Function) -> Result<(), ()> {
        let params = fct.params.iter().map(|p| param_header(p));
        let symfct = SymFunction(SymFunctionType {
            name: fct.name,
            return_type: fct.return_type.builtin,
            params: params.collect(),
            body: fct.block.id()
        });

        if let Err(_) = globals.insert(fct.name, symfct) {
            try!(self.error(ParseError {
                code: ErrorCode::IdentifierAlreadyExists,
                message: format!("identifier {} already exists", self.ast.str(fct.name)),
                position: fct.pos
            }));
        }

        Ok(())
    }

    fn check_function_body(&mut self, fct: &SymFunctionType) -> Result<(), ()> {
        Ok(())
    }

    fn error(&mut self, error: ParseError) -> Result<(), ()> {
        self.errors.push(error);

        if self.errors.len() <= MAX_ERRORS {
            Ok(())
        } else {
            Err(())
        }
    }

    fn check_function(&mut self, fct: &Function) {
        println!("check_function");
    }
}

fn param_header(param: &ast::Param) -> sym::Param {
    sym::Param {
        name: param.name,
        data_type: param.data_type.builtin
    }
}

#[test]
fn test_empty_file() {
    let prog = Parser::from_str("").parse().unwrap();

    SemCheck::new(&prog).check().unwrap();
}

#[test]
fn test_function_multiple_times() {
    let prog = Parser::from_str("fn main() {} fn main() {}").parse().unwrap();
    let errors = SemCheck::new(&prog).check().unwrap_err();

    assert_eq!(1, errors.len());
    assert_eq!(ErrorCode::IdentifierAlreadyExists, errors[0].code);
}

<commit_msg>semck: use is_empty instead of len() == 0<commit_after>use ast;
use ast::Ast;
use ast::Function;
use ast::Elem::ElemFunction;

use error::ErrorCode;
use error::ParseError;

use parser::Parser;

use sym;
use sym::SymTable;
use sym::Sym::SymFunction;
use sym::SymFunctionType;

macro_rules! err {
    ( $errs: expr, $x: expr ) => {
        {
            match $x {
                Ok(val) => val,
                Err(_) => { return Err($errs); }
            }
        }
    };
}

// Only do semantic analysis until some amount of
// errors found
static MAX_ERRORS: usize = 5;

pub struct SemCheck<'a> {
    ast: &'a Ast,
    errors: Vec<ParseError>,
}

impl<'a> SemCheck<'a> {
    pub fn new(ast: &Ast) -> SemCheck {
        SemCheck {
            ast: ast,
            errors: Vec::new(),
        }
    }

    pub fn check(mut self) -> Result<SymTable, Vec<ParseError>> {
        let mut globals = SymTable::new();

        err!(self.errors, self.parse_function_headers(&mut globals));
        err!(self.errors, self.parse_function_bodies(&mut globals));

        if self.errors.is_empty() {
            Ok(globals)
        } else {
            Err(self.errors)
        }
    }

    fn parse_function_headers(&mut self, globals: &mut SymTable) -> Result<(), ()> {
        for elem in &self.ast.elements {
            match *elem {
                ElemFunction(ref fct) => {
                    try!(self.add_function_header(globals, fct));
                }

                _ => unreachable!()
            }
        }

        Ok(())
    }

    fn parse_function_bodies(&mut self, globals: &mut SymTable) -> Result<(), ()> {
        for fct in globals.functions_mut() {
            try!(self.check_function_body(fct));
        }

        Ok(())
    }

    fn add_function_header(&mut self, globals: &mut SymTable, fct: &Function) -> Result<(), ()> {
        let params = fct.params.iter().map(|p| param_header(p));
        let symfct = SymFunction(SymFunctionType {
            name: fct.name,
            return_type: fct.return_type.builtin,
            params: params.collect(),
            body: fct.block.id()
        });

        if let Err(_) = globals.insert(fct.name, symfct) {
            try!(self.error(ParseError {
                code: ErrorCode::IdentifierAlreadyExists,
                message: format!("identifier {} already exists", self.ast.str(fct.name)),
                position: fct.pos
            }));
        }

        Ok(())
    }

    fn check_function_body(&mut self, fct: &SymFunctionType) -> Result<(), ()> {
        Ok(())
    }

    fn error(&mut self, error: ParseError) -> Result<(), ()> {
        self.errors.push(error);

        if self.errors.len() <= MAX_ERRORS {
            Ok(())
        } else {
            Err(())
        }
    }

    fn check_function(&mut self, fct: &Function) {
        println!("check_function");
    }
}

fn param_header(param: &ast::Param) -> sym::Param {
    sym::Param {
        name: param.name,
        data_type: param.data_type.builtin
    }
}

#[test]
fn test_empty_file() {
    let prog = Parser::from_str("").parse().unwrap();

    SemCheck::new(&prog).check().unwrap();
}

#[test]
fn test_function_multiple_times() {
    let prog = Parser::from_str("fn main() {} fn main() {}").parse().unwrap();
    let errors = SemCheck::new(&prog).check().unwrap_err();

    assert_eq!(1, errors.len());
    assert_eq!(ErrorCode::IdentifierAlreadyExists, errors[0].code);
}

<|endoftext|>


<commit_before>use crate::err::{self, Result};
use smallvec::{smallvec, SmallVec};
use snafu::ensure;
use std::convert::TryFrom;
use std::result;
use termion::event::Key;
use tui::style::{Color, Style};
use tui::widgets::Text;
use unicode_width::UnicodeWidthChar;

/// A prompt to enter commands in that provides suggestions.
pub struct CommandPrompt {
    buffer: String,
    hint_cmd: Option<HintCommand<'static>>,
    width: usize,
}

impl CommandPrompt {
    /// Create a new `CommandPrompt`.
    pub fn new() -> Self {
        CommandPrompt {
            buffer: String::with_capacity(32),
            hint_cmd: None,
            width: 0,
        }
    }

    /// Process a key for the `CommandPrompt`.
    ///
    /// Returns a `PromptResult` representing the state of the prompt based off of `key`.
    /// Example return values are given below:
    ///
    /// | `key`             | `PromptResult`             |
    /// | ----------------- | -------------------------- |
    /// | `Key::Char('a')`  | `PromptResult::NotDone`    |
    /// | `Key::Esc`        | `PromptResult::Done`       |
    /// | `Key::Char('\n')` | `PromptResult::Command(_)` |
    pub fn process_key(&mut self, key: Key) -> Result<PromptResult> {
        match key {
            Key::Char('\n') => {
                let command = Command::try_from(self.buffer.as_ref())?;
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Command(command));
            }
            Key::Char('\t') => {
                if let Some(hint_cmd) = &self.hint_cmd {
                    let remaining_name = hint_cmd.remaining_name();

                    self.buffer.push_str(remaining_name);
                    self.buffer.push(' ');
                    // Our hint text should always be ASCII, so we can skip getting the unicode width in this case
                    self.width += remaining_name.len() + 1;

                    self.hint_cmd = None;
                }
            }
            Key::Char(ch) => {
                self.buffer.push(ch);
                self.width += UnicodeWidthChar::width(ch).unwrap_or(0);

                self.hint_cmd = match Command::best_matching_cmd_info(&self.buffer) {
                    // Once again, our hint text should always be ASCII, so we don't care about the unicode width here as well
                    Some(matching_cmd) if self.buffer.len() <= matching_cmd.name.len() => {
                        let cmd = HintCommand::new(matching_cmd, self.buffer.len());
                        Some(cmd)
                    }
                    _ => None,
                };
            }
            Key::Backspace => {
                if let Some(popped) = self.buffer.pop() {
                    self.width -= UnicodeWidthChar::width(popped).unwrap_or(0);
                }

                self.hint_cmd = None;
            }
            Key::Esc => {
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Done);
            }
            _ => (),
        }

        Ok(PromptResult::NotDone)
    }

    #[inline(always)]
    pub fn width(&self) -> usize {
        self.width
    }

    /// The items of the `CommandPrompt` in a form ready for drawing.
    pub fn draw_items<'b>(&'b self) -> SmallVec<[Text<'b>; 2]> {
        let mut text = smallvec![Text::raw(&self.buffer)];

        if let Some(hint_cmd) = &self.hint_cmd {
            text.push(Text::styled(
                hint_cmd.remaining_name_and_usage(),
                Style::default().fg(Color::DarkGray),
            ));
        }

        text
    }
}

struct HintCommand<'a> {
    info: &'a CommandInfo,
    /// Represents the number of characters that have been "eaten" by user input.
    ///
    /// This is used so we can return a slice of the command's name and/or usage only
    /// containing the part that hasn't already been entered by the user.
    eaten: usize,
}

impl<'a> HintCommand<'a> {
    #[inline(always)]
    fn new(info: &'static CommandInfo, eaten: usize) -> Self {
        Self { info, eaten }
    }

    #[inline(always)]
    fn remaining_name(&self) -> &'a str {
        &self.info.name[self.eaten..]
    }

    #[inline(always)]
    fn remaining_name_and_usage(&self) -> &'a str {
        &self.info.name_and_usage[self.eaten..]
    }
}

struct CommandInfo {
    name: &'static str,
    name_and_usage: &'static str,
}

/// The result of processing a key in a `CommandPrompt`.
pub enum PromptResult {
    /// A successfully parsed command.
    Command(Command),
    /// Input is considered completed without a command.
    Done,
    /// More input is needed to reach a `Command(_)` or `Done` state.
    NotDone,
}

macro_rules! impl_command_matching {
    ($enum_name:ident, $num_cmds:expr, $($field:pat => { name: $name:expr, usage: $usage:expr, min_args: $min_args:expr, fn: $parse_fn:expr, },)+) => {
        impl $enum_name {
            const COMMANDS: [CommandInfo; $num_cmds] = [
                $(CommandInfo {
                    name: $name,
                    name_and_usage: concat!($name, " ", $usage),
                },)+
            ];
        }

        impl TryFrom<&str> for $enum_name {
            type Error = err::Error;

            fn try_from(value: &str) -> result::Result<Self, Self::Error> {
                let fragments = value.split_whitespace().collect::<SmallVec<[&str; 3]>>();

                ensure!(!fragments.is_empty(), err::NoCommandSpecified);

                let name = fragments[0].to_ascii_lowercase();
                let args = if fragments.len() > 1 {
                    &fragments[1..]
                } else {
                    &[]
                };

                match name.as_ref() {
                    $($name => {
                        #[allow(unused_comparisons)]
                        let has_min_args = args.len() >= $min_args;

                        ensure!(
                            has_min_args,
                            err::NotEnoughArguments {
                                has: args.len(),
                                need: $min_args as usize,
                            }
                        );

                        $parse_fn(args)
                    },)+
                    _ => Err(err::Error::CommandNotFound {
                        command: value.into(),
                    }),
                }
            }
        }
    };
}

/// A parsed command with its arguments.
#[derive(Debug, Clone)]
pub enum Command {
    /// Add a new series with the specified nickname and optional series ID.
    Add(String, Option<anime::remote::SeriesID>),
    /// Remove the selected series from the program.
    Delete,
    /// Set the user's login token for a remote service.
    LoginToken(String),
    /// Set the episode matcher for the selected series.
    Matcher(Option<String>),
    /// Specify the video player arguments for the selected season.
    PlayerArgs(Vec<String>),
    /// Increment / decrement the watched episodes of the selected season.
    Progress(ProgressDirection),
    /// Syncronize the selected season to the remote service.
    SyncFromRemote,
    /// Syncronize the selected season from the remote service.
    SyncToRemote,
    /// Rate the selected season.
    Score(String),
    /// Set the watch status of the selected season.
    Status(anime::remote::Status),
}

impl_command_matching!(Command, 10,
    Add(_) => {
        name: "add",
        usage: "<nickname> [id]",
        min_args: 1,
        fn: |args: &[&str]| {
            let id = if args.len() > 1 {
                args[1].parse().ok()
            } else {
                None
            };

            Ok(Command::Add(args[0].into(), id))
        },
    },
    Delete => {
        name: "delete",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::Delete),
    },
    LoginToken(_) => {
        name: "token",
        usage: "<token>",
        min_args: 1,
        fn: |args: &[&str]| {
            Ok(Command::LoginToken(args.join(" ")))
        },
    },
    Matcher(_) => {
        name: "matcher",
        usage: "[regex containing {title} and {episode}]",
        min_args: 0,
        fn: |args: &[&str]| {
            if args.is_empty() {
                Ok(Command::Matcher(None))
            } else {
                Ok(Command::Matcher(Some(args.join(" "))))
            }
        },
    },
    PlayerArgs(_) => {
        name: "args",
        usage: "<player args>",
        min_args: 0,
        fn: |args: &[&str]| {
            let args = args.iter()
                .map(|frag| frag.to_string())
                .collect();

            Ok(Command::PlayerArgs(args))
        },
    },
    Progress(_) => {
        name: "progress",
        usage: "<f, forward | b, backward>",
        min_args: 1,
        fn: |args: &[&str]| {
            let dir = ProgressDirection::try_from(args[0])?;
            Ok(Command::Progress(dir))
        },
    },
    SyncFromRemote => {
        name: "syncfromremote",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::SyncFromRemote),
    },
    SyncToRemote => {
        name: "synctoremote",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::SyncToRemote),
    },
    Score(_) => {
        name: "rate",
        usage: "<0-100>",
        min_args: 1,
        fn: |args: &[&str]| {
            let score = args[0].into();
            Ok(Command::Score(score))
        },
    },
    Status(_) => {
        name: "status",
        usage: "<w, watching | c, completed | h, hold | d, drop | p, plan | r, rewatch>",
        min_args: 1,
        fn: |args: &[&str]| {
            use anime::remote::Status;

            let status = match args[0].to_ascii_lowercase().as_ref() {
                "w" | "watching" => Status::Watching,
                "c" | "completed" => Status::Completed,
                "h" | "hold" => Status::OnHold,
                "d" | "drop" => Status::Dropped,
                "p" | "plan" => Status::PlanToWatch,
                "r" | "rewatch" => Status::Rewatching,
                _ => {
                    return Err(err::Error::UnknownCmdPromptArg {
                        value: args[0].into(),
                    })
                }
            };

            Ok(Command::Status(status))
        },
    },
);

impl Command {
    /// Returns the `CommandInfo` that has a name most similar to `name`.
    ///
    /// `None` will be returned if `name` does not match a command name with
    /// at least 70% similarity.
    fn best_matching_cmd_info(name: &str) -> Option<&'static CommandInfo> {
        const MIN_CONFIDENCE: f32 = 0.7;

        detect::closest_match(&Command::COMMANDS, MIN_CONFIDENCE, |cmd| {
            Some(strsim::jaro_winkler(&cmd.name, name) as f32)
        })
    }
}

/// Indicates which way to advance the episode count of a season.
#[derive(Debug, Copy, Clone)]
pub enum ProgressDirection {
    /// Increase the episode count.
    Forwards,
    /// Decrease the episode count.
    Backwards,
}

impl TryFrom<&str> for ProgressDirection {
    type Error = err::Error;

    fn try_from(value: &str) -> result::Result<Self, Self::Error> {
        match value {
            "f" | "forward" => Ok(ProgressDirection::Forwards),
            "b" | "backward" => Ok(ProgressDirection::Backwards),
            _ => Err(err::Error::UnknownCmdPromptArg {
                value: value.into(),
            }),
        }
    }
}

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

    #[test]
    fn test_commands() {
        use anime::remote::Status;

        let mut prompt = CommandPrompt::new();

        let mut enter_command = |name: &str| {
            for ch in name.chars() {
                match prompt.process_key(Key::Char(ch)) {
                    Ok(PromptResult::NotDone) => (),
                    Ok(PromptResult::Done) => panic!("expected {} command, got nothing", name),
                    Ok(PromptResult::Command(cmd)) => return cmd,
                    Err(err) => panic!("error processing command: {}", err),
                }
            }

            panic!("unexpected finish when entering \"{}\"", name);
        };

        macro_rules! expected {
            ($got:expr, $wanted:expr) => {
                panic!("got unexpected command: {:?}, wanted  {:?}", $got, $wanted)
            };
        }

        macro_rules! test_command {
            ($name:expr, $should_eq:pat) => {
                match enter_command($name) {
                    $should_eq => (),
                    other => expected!(other, stringify!($should_eq)),
                }
            };
        }

        test_command!("delete\n", Command::Delete);

        match enter_command("token inserttokenhere\n") {
            Command::LoginToken(token) if token == "inserttokenhere" => (),
            other => expected!(other, Command::LoginToken(String::from("inserttokenhere"))),
        }

        let expected_args: Vec<String> = vec!["arg1".into(), "arg2".into()];

        match enter_command("args arg1 arg2\n") {
            Command::PlayerArgs(args) => {
                if args.len() != expected_args.len() {
                    expected!(
                        Command::PlayerArgs(args),
                        Command::PlayerArgs(expected_args)
                    );
                }

                for (parsed, expected_arg) in args.iter().zip(&expected_args) {
                    if *parsed != *expected_arg {
                        expected!(
                            Command::PlayerArgs(args),
                            Command::PlayerArgs(expected_args)
                        );
                    }
                }
            }
            other => expected!(other, Command::PlayerArgs(expected_args)),
        }

        test_command!(
            "progress forward\n",
            Command::Progress(ProgressDirection::Forwards)
        );

        test_command!("status watching\n", Command::Status(Status::Watching));
    }
}
<commit_msg>anup: parse command prompt input as shell words<commit_after>use crate::err::{self, Result};
use smallvec::{smallvec, SmallVec};
use snafu::ensure;
use std::convert::TryFrom;
use std::result;
use termion::event::Key;
use tui::style::{Color, Style};
use tui::widgets::Text;
use unicode_width::UnicodeWidthChar;

/// A prompt to enter commands in that provides suggestions.
pub struct CommandPrompt {
    buffer: String,
    hint_cmd: Option<HintCommand<'static>>,
    width: usize,
}

impl CommandPrompt {
    /// Create a new `CommandPrompt`.
    pub fn new() -> Self {
        CommandPrompt {
            buffer: String::with_capacity(32),
            hint_cmd: None,
            width: 0,
        }
    }

    /// Process a key for the `CommandPrompt`.
    ///
    /// Returns a `PromptResult` representing the state of the prompt based off of `key`.
    /// Example return values are given below:
    ///
    /// | `key`             | `PromptResult`             |
    /// | ----------------- | -------------------------- |
    /// | `Key::Char('a')`  | `PromptResult::NotDone`    |
    /// | `Key::Esc`        | `PromptResult::Done`       |
    /// | `Key::Char('\n')` | `PromptResult::Command(_)` |
    pub fn process_key(&mut self, key: Key) -> Result<PromptResult> {
        match key {
            Key::Char('\n') => {
                let command = Command::try_from(self.buffer.as_ref())?;
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Command(command));
            }
            Key::Char('\t') => {
                if let Some(hint_cmd) = &self.hint_cmd {
                    let remaining_name = hint_cmd.remaining_name();

                    self.buffer.push_str(remaining_name);
                    self.buffer.push(' ');
                    // Our hint text should always be ASCII, so we can skip getting the unicode width in this case
                    self.width += remaining_name.len() + 1;

                    self.hint_cmd = None;
                }
            }
            Key::Char(ch) => {
                self.buffer.push(ch);
                self.width += UnicodeWidthChar::width(ch).unwrap_or(0);

                self.hint_cmd = match Command::best_matching_cmd_info(&self.buffer) {
                    // Once again, our hint text should always be ASCII, so we don't care about the unicode width here as well
                    Some(matching_cmd) if self.buffer.len() <= matching_cmd.name.len() => {
                        let cmd = HintCommand::new(matching_cmd, self.buffer.len());
                        Some(cmd)
                    }
                    _ => None,
                };
            }
            Key::Backspace => {
                if let Some(popped) = self.buffer.pop() {
                    self.width -= UnicodeWidthChar::width(popped).unwrap_or(0);
                }

                self.hint_cmd = None;
            }
            Key::Esc => {
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Done);
            }
            _ => (),
        }

        Ok(PromptResult::NotDone)
    }

    #[inline(always)]
    pub fn width(&self) -> usize {
        self.width
    }

    /// The items of the `CommandPrompt` in a form ready for drawing.
    pub fn draw_items<'b>(&'b self) -> SmallVec<[Text<'b>; 2]> {
        let mut text = smallvec![Text::raw(&self.buffer)];

        if let Some(hint_cmd) = &self.hint_cmd {
            text.push(Text::styled(
                hint_cmd.remaining_name_and_usage(),
                Style::default().fg(Color::DarkGray),
            ));
        }

        text
    }
}

struct HintCommand<'a> {
    info: &'a CommandInfo,
    /// Represents the number of characters that have been "eaten" by user input.
    ///
    /// This is used so we can return a slice of the command's name and/or usage only
    /// containing the part that hasn't already been entered by the user.
    eaten: usize,
}

impl<'a> HintCommand<'a> {
    #[inline(always)]
    fn new(info: &'static CommandInfo, eaten: usize) -> Self {
        Self { info, eaten }
    }

    #[inline(always)]
    fn remaining_name(&self) -> &'a str {
        &self.info.name[self.eaten..]
    }

    #[inline(always)]
    fn remaining_name_and_usage(&self) -> &'a str {
        &self.info.name_and_usage[self.eaten..]
    }
}

struct CommandInfo {
    name: &'static str,
    name_and_usage: &'static str,
}

/// The result of processing a key in a `CommandPrompt`.
pub enum PromptResult {
    /// A successfully parsed command.
    Command(Command),
    /// Input is considered completed without a command.
    Done,
    /// More input is needed to reach a `Command(_)` or `Done` state.
    NotDone,
}

fn char_is_quote(ch: char) -> bool {
    ch == '\"' || ch == '\''
}

/// Split `string` into shell words.
///
/// This implementation only groups (non-nested) quotes into one argument.
fn simple_shell_words<'a>(string: &'a str) -> SmallVec<[&'a str; 3]> {
    if string.is_empty() {
        return SmallVec::new();
    }

    let mut slices = SmallVec::new();
    let mut start = 0;
    let mut in_quote = false;

    let is_surrounded_by_quotes = |slice: &str| {
        if slice.len() < 2 {
            return false;
        }

        slice.starts_with(char_is_quote) && slice.ends_with(char_is_quote)
    };

    let mut push_slice = |start, end| {
        let mut slice = &string[start..end];

        if is_surrounded_by_quotes(slice) {
            slice = &slice[1..slice.len() - 1];
        }

        if slice.is_empty() {
            return;
        }

        slices.push(slice);
    };

    for (i, ch) in string.chars().enumerate() {
        match ch {
            ' ' => {
                if in_quote {
                    continue;
                }

                push_slice(start, i);
                start = i + 1;
            }
            '\"' | '\'' => in_quote = !in_quote,
            _ => (),
        }
    }

    push_slice(start, string.len());
    slices
}

macro_rules! impl_command_matching {
    ($enum_name:ident, $num_cmds:expr, $($field:pat => { name: $name:expr, usage: $usage:expr, min_args: $min_args:expr, fn: $parse_fn:expr, },)+) => {
        impl $enum_name {
            const COMMANDS: [CommandInfo; $num_cmds] = [
                $(CommandInfo {
                    name: $name,
                    name_and_usage: concat!($name, " ", $usage),
                },)+
            ];
        }

        impl TryFrom<&str> for $enum_name {
            type Error = err::Error;

            fn try_from(value: &str) -> result::Result<Self, Self::Error> {
                let fragments = simple_shell_words(value);
                ensure!(!fragments.is_empty(), err::NoCommandSpecified);

                let name = fragments[0].to_ascii_lowercase();
                let args = if fragments.len() > 1 {
                    &fragments[1..]
                } else {
                    &[]
                };

                match name.as_ref() {
                    $($name => {
                        #[allow(unused_comparisons)]
                        let has_min_args = args.len() >= $min_args;

                        ensure!(
                            has_min_args,
                            err::NotEnoughArguments {
                                has: args.len(),
                                need: $min_args as usize,
                            }
                        );

                        $parse_fn(args)
                    },)+
                    _ => Err(err::Error::CommandNotFound {
                        command: value.into(),
                    }),
                }
            }
        }
    };
}

/// A parsed command with its arguments.
#[derive(Debug, Clone)]
pub enum Command {
    /// Add a new series with the specified nickname and optional series ID.
    Add(String, Option<anime::remote::SeriesID>),
    /// Remove the selected series from the program.
    Delete,
    /// Set the user's login token for a remote service.
    LoginToken(String),
    /// Set the episode matcher for the selected series.
    Matcher(Option<String>),
    /// Specify the video player arguments for the selected season.
    PlayerArgs(Vec<String>),
    /// Increment / decrement the watched episodes of the selected season.
    Progress(ProgressDirection),
    /// Syncronize the selected season to the remote service.
    SyncFromRemote,
    /// Syncronize the selected season from the remote service.
    SyncToRemote,
    /// Rate the selected season.
    Score(String),
    /// Set the watch status of the selected season.
    Status(anime::remote::Status),
}

impl_command_matching!(Command, 10,
    Add(_) => {
        name: "add",
        usage: "<nickname> [id]",
        min_args: 1,
        fn: |args: &[&str]| {
            let id = if args.len() > 1 {
                args[1].parse().ok()
            } else {
                None
            };

            Ok(Command::Add(args[0].into(), id))
        },
    },
    Delete => {
        name: "delete",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::Delete),
    },
    LoginToken(_) => {
        name: "token",
        usage: "<token>",
        min_args: 1,
        fn: |args: &[&str]| {
            Ok(Command::LoginToken(args.join(" ")))
        },
    },
    Matcher(_) => {
        name: "matcher",
        usage: "[regex containing {title} and {episode}]",
        min_args: 0,
        fn: |args: &[&str]| {
            if args.is_empty() {
                Ok(Command::Matcher(None))
            } else {
                Ok(Command::Matcher(Some(args.join(" "))))
            }
        },
    },
    PlayerArgs(_) => {
        name: "args",
        usage: "<player args>",
        min_args: 0,
        fn: |args: &[&str]| {
            let args = args.iter()
                .map(|frag| frag.to_string())
                .collect();

            Ok(Command::PlayerArgs(args))
        },
    },
    Progress(_) => {
        name: "progress",
        usage: "<f, forward | b, backward>",
        min_args: 1,
        fn: |args: &[&str]| {
            let dir = ProgressDirection::try_from(args[0])?;
            Ok(Command::Progress(dir))
        },
    },
    SyncFromRemote => {
        name: "syncfromremote",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::SyncFromRemote),
    },
    SyncToRemote => {
        name: "synctoremote",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::SyncToRemote),
    },
    Score(_) => {
        name: "rate",
        usage: "<0-100>",
        min_args: 1,
        fn: |args: &[&str]| {
            let score = args[0].into();
            Ok(Command::Score(score))
        },
    },
    Status(_) => {
        name: "status",
        usage: "<w, watching | c, completed | h, hold | d, drop | p, plan | r, rewatch>",
        min_args: 1,
        fn: |args: &[&str]| {
            use anime::remote::Status;

            let status = match args[0].to_ascii_lowercase().as_ref() {
                "w" | "watching" => Status::Watching,
                "c" | "completed" => Status::Completed,
                "h" | "hold" => Status::OnHold,
                "d" | "drop" => Status::Dropped,
                "p" | "plan" => Status::PlanToWatch,
                "r" | "rewatch" => Status::Rewatching,
                _ => {
                    return Err(err::Error::UnknownCmdPromptArg {
                        value: args[0].into(),
                    })
                }
            };

            Ok(Command::Status(status))
        },
    },
);

impl Command {
    /// Returns the `CommandInfo` that has a name most similar to `name`.
    ///
    /// `None` will be returned if `name` does not match a command name with
    /// at least 70% similarity.
    fn best_matching_cmd_info(name: &str) -> Option<&'static CommandInfo> {
        const MIN_CONFIDENCE: f32 = 0.7;

        detect::closest_match(&Command::COMMANDS, MIN_CONFIDENCE, |cmd| {
            Some(strsim::jaro_winkler(&cmd.name, name) as f32)
        })
    }
}

/// Indicates which way to advance the episode count of a season.
#[derive(Debug, Copy, Clone)]
pub enum ProgressDirection {
    /// Increase the episode count.
    Forwards,
    /// Decrease the episode count.
    Backwards,
}

impl TryFrom<&str> for ProgressDirection {
    type Error = err::Error;

    fn try_from(value: &str) -> result::Result<Self, Self::Error> {
        match value {
            "f" | "forward" => Ok(ProgressDirection::Forwards),
            "b" | "backward" => Ok(ProgressDirection::Backwards),
            _ => Err(err::Error::UnknownCmdPromptArg {
                value: value.into(),
            }),
        }
    }
}

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

    #[test]
    fn test_commands() {
        use anime::remote::Status;

        let mut prompt = CommandPrompt::new();

        let mut enter_command = |name: &str| {
            for ch in name.chars() {
                match prompt.process_key(Key::Char(ch)) {
                    Ok(PromptResult::NotDone) => (),
                    Ok(PromptResult::Done) => panic!("expected {} command, got nothing", name),
                    Ok(PromptResult::Command(cmd)) => return cmd,
                    Err(err) => panic!("error processing command: {}", err),
                }
            }

            panic!("unexpected finish when entering \"{}\"", name);
        };

        macro_rules! expected {
            ($got:expr, $wanted:expr) => {
                panic!("got unexpected command: {:?}, wanted  {:?}", $got, $wanted)
            };
        }

        macro_rules! test_command {
            ($name:expr, $should_eq:pat) => {
                match enter_command($name) {
                    $should_eq => (),
                    other => expected!(other, stringify!($should_eq)),
                }
            };
        }

        test_command!("delete\n", Command::Delete);

        match enter_command("token inserttokenhere\n") {
            Command::LoginToken(token) if token == "inserttokenhere" => (),
            other => expected!(other, Command::LoginToken(String::from("inserttokenhere"))),
        }

        let expected_args: Vec<String> = vec!["arg1".into(), "arg2".into()];

        match enter_command("args arg1 arg2\n") {
            Command::PlayerArgs(args) => {
                if args.len() != expected_args.len() {
                    expected!(
                        Command::PlayerArgs(args),
                        Command::PlayerArgs(expected_args)
                    );
                }

                for (parsed, expected_arg) in args.iter().zip(&expected_args) {
                    if *parsed != *expected_arg {
                        expected!(
                            Command::PlayerArgs(args),
                            Command::PlayerArgs(expected_args)
                        );
                    }
                }
            }
            other => expected!(other, Command::PlayerArgs(expected_args)),
        }

        test_command!(
            "progress forward\n",
            Command::Progress(ProgressDirection::Forwards)
        );

        test_command!("status watching\n", Command::Status(Status::Watching));
    }

    #[test]
    fn test_shell_words() {
        use smallvec::smallvec;

        let expected: SmallVec<[_; 4]> = smallvec!["this", "is", "a", "test"];
        assert_eq!(simple_shell_words("this is a test"), expected);

        let expected: SmallVec<[_; 3]> = smallvec!["this", "is a harder", "test"];
        assert_eq!(simple_shell_words("this \"is a harder\" test"), expected);

        let expected: SmallVec<[_; 4]> =
            smallvec!["this", "tests=\"quotes inside of\"", "one", "string"];
        assert_eq!(
            simple_shell_words("this tests=\"quotes inside of\" one string"),
            expected
        );

        let expected: SmallVec<[_; 3]> = smallvec!["tests", "empty", "quotes"];
        assert_eq!(simple_shell_words("tests \"\" empty quotes"), expected);

        let expected: SmallVec<[_; 1]> = smallvec!["tests single quote with spaces"];
        assert_eq!(
            simple_shell_words("\"tests single quote with spaces\""),
            expected
        );

        let expected: SmallVec<[_; 3]> = smallvec!["tests", "alternative quote", "matcher"];
        assert_eq!(
            simple_shell_words("tests \'alternative quote\' matcher"),
            expected
        );

        let expected: SmallVec<[_; 3]> = smallvec!["tests", "having mixed", "quotes"];
        assert_eq!(
            simple_shell_words("tests \"having mixed\' quotes"),
            expected
        );

        // Only one quote
        let expected: SmallVec<[_; 1]> = smallvec!["\""];
        assert_eq!(simple_shell_words("\""), expected);

        // Only one space
        let expected: SmallVec<[&str; 0]> = smallvec![];
        assert_eq!(simple_shell_words(" "), expected);

        // Empty quotes without any other arguments
        assert_eq!(simple_shell_words("\"\""), expected);
    }
}
<|endoftext|>


<commit_before>use slack_hook::{Attachment, AttachmentBuilder, Field, PayloadBuilder, Slack, SlackText, SlackLink};
use slack_hook::SlackTextContent::{Text, Link};

use taste::{BenchmarkResult, TastingResult};

pub struct SlackNotifier {
    conn: Slack,
    channel: String,
    github_repo: String,
}

impl SlackNotifier {
    pub fn new(hook_url: &str, channel: &str, repo_url: &str) -> SlackNotifier {
        SlackNotifier {
            conn: Slack::new(hook_url).unwrap(),
            channel: String::from(channel),
            github_repo: String::from(repo_url),
        }
    }

    pub fn notify(&self, res: &TastingResult) -> Result<(), String> {
        println!("{:#?}", res);
        let payload = PayloadBuilder::new()
            .text(vec![Text("I've tasted commit _".into()),
                       Text(format!("\"{}\"_ -- ", res.commit_msg.lines().next().unwrap())
                           .into()),
                       Link(SlackLink::new(&format!("{}/commit/{}",
                                                    self.github_repo,
                                                    res.commit_id),
                                           &res.commit_id[0..6]))]
                .as_slice())
            .attachments(result_to_attachments(&res))
            .channel(self.channel.clone())
            .username("taster")
            .icon_emoji(":tea:")
            .build()
            .unwrap();

        match self.conn.send(&payload) {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("{:?}", e)),
        }
    }
}

fn result_to_attachments(res: &TastingResult) -> Vec<Attachment> {
    let color = if !res.build || !res.bench {
        "danger"
    } else {
        "good"
    };

    let title = if !res.build {
        "Build failure!"
    } else if !res.bench {
        "Benchmark failure!"
    } else {
        "Performance results:"
    };

    let taste = if !res.build || !res.bench {
        "was inedible"
    } else {
        "tasted nice"
    };

    let mut attachments = Vec::new();
    let build_att = AttachmentBuilder::new("")
        .title(format!("It {}.", taste))
        .fields(vec![Field {
                         title: "Build".into(),
                         value: SlackText::new(if res.build { "success" } else { "failed" }),
                         short: Some(true),
                     },
                     Field {
                         title: "Benchmark".into(),
                         value: SlackText::new(if res.bench { "success" } else { "failed" }),
                         short: Some(true),
                     }])
        .color(color)
        .build()
        .unwrap();
    attachments.push(build_att);

    match res.results {
        None => (),
        Some(ref r) => {
            for res in r {
                let mut nv = res.iter()
                    .map(|(k, v)| {
                        let val = match v {
                            &BenchmarkResult::Neutral(ref s, _) => s,
                            _ => unimplemented!(),
                        };
                        Field {
                            title: k.clone(),
                            value: SlackText::new(val.clone()),
                            short: Some(true),
                        }
                    })
                    .collect::<Vec<_>>();
                nv.sort_by(|a, b| b.title.cmp(&a.title));
                let att = AttachmentBuilder::new(format!("It {}.", taste))
                    .color(color)
                    .title(title)
                    .fields(nv)
                    .build()
                    .unwrap();
                attachments.push(att);
            }
        }
    }
    attachments
}
<commit_msg>Drop debug print.<commit_after>use slack_hook::{Attachment, AttachmentBuilder, Field, PayloadBuilder, Slack, SlackText, SlackLink};
use slack_hook::SlackTextContent::{Text, Link};

use taste::{BenchmarkResult, TastingResult};

pub struct SlackNotifier {
    conn: Slack,
    channel: String,
    github_repo: String,
}

impl SlackNotifier {
    pub fn new(hook_url: &str, channel: &str, repo_url: &str) -> SlackNotifier {
        SlackNotifier {
            conn: Slack::new(hook_url).unwrap(),
            channel: String::from(channel),
            github_repo: String::from(repo_url),
        }
    }

    pub fn notify(&self, res: &TastingResult) -> Result<(), String> {
        let payload = PayloadBuilder::new()
            .text(vec![Text("I've tasted commit _".into()),
                       Text(format!("\"{}\"_ -- ", res.commit_msg.lines().next().unwrap())
                           .into()),
                       Link(SlackLink::new(&format!("{}/commit/{}",
                                                    self.github_repo,
                                                    res.commit_id),
                                           &res.commit_id[0..6]))]
                .as_slice())
            .attachments(result_to_attachments(&res))
            .channel(self.channel.clone())
            .username("taster")
            .icon_emoji(":tea:")
            .build()
            .unwrap();

        match self.conn.send(&payload) {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("{:?}", e)),
        }
    }
}

fn result_to_attachments(res: &TastingResult) -> Vec<Attachment> {
    let color = if !res.build || !res.bench {
        "danger"
    } else {
        "good"
    };

    let title = if !res.build {
        "Build failure!"
    } else if !res.bench {
        "Benchmark failure!"
    } else {
        "Performance results:"
    };

    let taste = if !res.build || !res.bench {
        "was inedible"
    } else {
        "tasted nice"
    };

    let mut attachments = Vec::new();
    let build_att = AttachmentBuilder::new("")
        .title(format!("It {}.", taste))
        .fields(vec![Field {
                         title: "Build".into(),
                         value: SlackText::new(if res.build { "success" } else { "failed" }),
                         short: Some(true),
                     },
                     Field {
                         title: "Benchmark".into(),
                         value: SlackText::new(if res.bench { "success" } else { "failed" }),
                         short: Some(true),
                     }])
        .color(color)
        .build()
        .unwrap();
    attachments.push(build_att);

    match res.results {
        None => (),
        Some(ref r) => {
            for res in r {
                let mut nv = res.iter()
                    .map(|(k, v)| {
                        let val = match v {
                            &BenchmarkResult::Neutral(ref s, _) => s,
                            _ => unimplemented!(),
                        };
                        Field {
                            title: k.clone(),
                            value: SlackText::new(val.clone()),
                            short: Some(true),
                        }
                    })
                    .collect::<Vec<_>>();
                nv.sort_by(|a, b| b.title.cmp(&a.title));
                let att = AttachmentBuilder::new(format!("It {}.", taste))
                    .color(color)
                    .title(title)
                    .fields(nv)
                    .build()
                    .unwrap();
                attachments.push(att);
            }
        }
    }
    attachments
}
<|endoftext|>


<commit_before>use std::collections::{BTreeMap, HashMap, HashSet};

use term::Term;
use mapping::{Mapping, FieldMapping};
use document::Document;

#[derive(Debug)]
pub struct Index {
    pub mappings: HashMap<String, Mapping>,
    pub docs: BTreeMap<u64, Document>,
    pub index: BTreeMap<Term, BTreeMap<String, Vec<(u64, u32)>>>,
    pub aliases: HashSet<String>,
    next_doc_num: u64,
    doc_id_map: HashMap<String, u64>,
}

impl Index {
    pub fn new() -> Index {
        Index {
            mappings: HashMap::new(),
            docs: BTreeMap::new(),
            index: BTreeMap::new(),
            aliases: HashSet::new(),
            next_doc_num: 1,
            doc_id_map: HashMap::new(),
        }
    }

    pub fn get_mapping_by_name(&self, name: &str) -> Option<&Mapping> {
        self.mappings.get(name)
    }

    pub fn get_field_mapping_by_name(&self, name: &str) -> Option<&FieldMapping> {
        for mapping in self.mappings.values() {
            if let Some(ref field_mapping) = mapping.fields.get(name) {
                return Some(field_mapping);
            }
        }

        None
    }

    pub fn get_document_by_id(&self, id: &str) -> Option<&Document> {
        match self.doc_id_map.get(id) {
            Some(doc_num) => self.docs.get(doc_num),
            None => None,
        }
    }

    pub fn contains_document_id(&self, id: &str) -> bool {
        self.doc_id_map.contains_key(id)
    }

    pub fn remove_document_by_id(&mut self, id: &str) -> bool {
        match self.doc_id_map.remove(id) {
            Some(doc_num) => {
                self.docs.remove(&doc_num);

                true
            }
            None => false
        }
    }

    pub fn insert_or_update_document(&mut self, doc: Document) {
        let doc_num = self.next_doc_num;
        self.next_doc_num += 1;

        // Put field contents in inverted index
        for (field_name, tokens) in doc.fields.iter() {
            let mut position: u32 = 1;

            for token in tokens.iter() {
                if !self.index.contains_key(&token.term) {
                    self.index.insert(token.term.clone(), BTreeMap::new());
                }

                let mut index_fields = self.index.get_mut(&token.term).unwrap();

                if !index_fields.contains_key(field_name) {
                    index_fields.insert(field_name.clone(), Vec::new());
                }

                let mut postings_list = index_fields.get_mut(field_name).unwrap();
                postings_list.push((doc_num, token.position));
            }
        }

        self.doc_id_map.insert(doc.id.clone(), doc_num);
        self.docs.insert(doc_num, doc);
    }

    pub fn next_phrase(&self, terms: &Vec<Term>, field_name: &str, position: Option<(u64, u32)>) -> Option<(u64, u32)> {
        let mut v = position;

        for term in terms.iter() {
            v = self.next(term, field_name, v);

            if v == None {
                return None;
            }
        }

        let mut u = v;
        for term in terms.iter().rev().skip(1) {
            u = self.prev(term, field_name, u);

            if u == None {
                return None;
            }
        }

        let v = v.unwrap();
        let u = u.unwrap();
        if v.0 == u.0 && v.1 - u.1 == terms.len() as u32 - 1 {
            Some((v.0, u.1))
        } else {
            self.next_phrase(terms, field_name, Some(u))
        }
    }

    pub fn next(&self, term: &Term, field_name: &str, position: Option<(u64, u32)>) -> Option<(u64, u32)> {
        let posting_columns = match self.index.get(term) {
            Some(posting_columns) => posting_columns,
            None => return None,
        };

        let postings = match posting_columns.get(field_name) {
            Some(postings) => postings,
            None => return None,
        };

        match position {
            Some(position) => {
                // Find first posting after specified position
                // TODO: Speed this up (see section 2.1.2 of the IR book)
                for posting in postings.iter() {
                    if *posting > position {
                        return Some(posting.clone());
                    }
                }

                // Ran out of postings
                return None;
            }
            None => {
                // Position not specified, return first posting
                return postings.first().cloned();
            }
        }
    }

    pub fn prev(&self, term: &Term, field_name: &str, position: Option<(u64, u32)>) -> Option<(u64, u32)> {
        let posting_columns = match self.index.get(term) {
            Some(posting_columns) => posting_columns,
            None => return None,
        };

        let postings = match posting_columns.get(field_name) {
            Some(postings) => postings,
            None => return None,
        };

        match position {
            Some(position) => {
                // Find first posting before specified position
                // TODO: Speed this up (see section 2.1.2 of the IR book)
                for posting in postings.iter().rev() {
                    if *posting < position {
                        return Some(posting.clone());
                    }
                }

                // Ran out of postings
                return None;
            }
            None => {
                // Position not specified, return last posting
                return postings.last().cloned();
            }
        }
    }

    pub fn initialise(&mut self) {}
}
<commit_msg>Terminology fix: column => field<commit_after>use std::collections::{BTreeMap, HashMap, HashSet};

use term::Term;
use mapping::{Mapping, FieldMapping};
use document::Document;

#[derive(Debug)]
pub struct Index {
    pub mappings: HashMap<String, Mapping>,
    pub docs: BTreeMap<u64, Document>,
    pub index: BTreeMap<Term, BTreeMap<String, Vec<(u64, u32)>>>,
    pub aliases: HashSet<String>,
    next_doc_num: u64,
    doc_id_map: HashMap<String, u64>,
}

impl Index {
    pub fn new() -> Index {
        Index {
            mappings: HashMap::new(),
            docs: BTreeMap::new(),
            index: BTreeMap::new(),
            aliases: HashSet::new(),
            next_doc_num: 1,
            doc_id_map: HashMap::new(),
        }
    }

    pub fn get_mapping_by_name(&self, name: &str) -> Option<&Mapping> {
        self.mappings.get(name)
    }

    pub fn get_field_mapping_by_name(&self, name: &str) -> Option<&FieldMapping> {
        for mapping in self.mappings.values() {
            if let Some(ref field_mapping) = mapping.fields.get(name) {
                return Some(field_mapping);
            }
        }

        None
    }

    pub fn get_document_by_id(&self, id: &str) -> Option<&Document> {
        match self.doc_id_map.get(id) {
            Some(doc_num) => self.docs.get(doc_num),
            None => None,
        }
    }

    pub fn contains_document_id(&self, id: &str) -> bool {
        self.doc_id_map.contains_key(id)
    }

    pub fn remove_document_by_id(&mut self, id: &str) -> bool {
        match self.doc_id_map.remove(id) {
            Some(doc_num) => {
                self.docs.remove(&doc_num);

                true
            }
            None => false
        }
    }

    pub fn insert_or_update_document(&mut self, doc: Document) {
        let doc_num = self.next_doc_num;
        self.next_doc_num += 1;

        // Put field contents in inverted index
        for (field_name, tokens) in doc.fields.iter() {
            let mut position: u32 = 1;

            for token in tokens.iter() {
                if !self.index.contains_key(&token.term) {
                    self.index.insert(token.term.clone(), BTreeMap::new());
                }

                let mut index_fields = self.index.get_mut(&token.term).unwrap();

                if !index_fields.contains_key(field_name) {
                    index_fields.insert(field_name.clone(), Vec::new());
                }

                let mut postings_list = index_fields.get_mut(field_name).unwrap();
                postings_list.push((doc_num, token.position));
            }
        }

        self.doc_id_map.insert(doc.id.clone(), doc_num);
        self.docs.insert(doc_num, doc);
    }

    pub fn next_phrase(&self, terms: &Vec<Term>, field_name: &str, position: Option<(u64, u32)>) -> Option<(u64, u32)> {
        let mut v = position;

        for term in terms.iter() {
            v = self.next(term, field_name, v);

            if v == None {
                return None;
            }
        }

        let mut u = v;
        for term in terms.iter().rev().skip(1) {
            u = self.prev(term, field_name, u);

            if u == None {
                return None;
            }
        }

        let v = v.unwrap();
        let u = u.unwrap();
        if v.0 == u.0 && v.1 - u.1 == terms.len() as u32 - 1 {
            Some((v.0, u.1))
        } else {
            self.next_phrase(terms, field_name, Some(u))
        }
    }

    pub fn next(&self, term: &Term, field_name: &str, position: Option<(u64, u32)>) -> Option<(u64, u32)> {
        let posting_fields = match self.index.get(term) {
            Some(posting_fields) => posting_fields,
            None => return None,
        };

        let postings = match posting_fields.get(field_name) {
            Some(postings) => postings,
            None => return None,
        };

        match position {
            Some(position) => {
                // Find first posting after specified position
                // TODO: Speed this up (see section 2.1.2 of the IR book)
                for posting in postings.iter() {
                    if *posting > position {
                        return Some(posting.clone());
                    }
                }

                // Ran out of postings
                return None;
            }
            None => {
                // Position not specified, return first posting
                return postings.first().cloned();
            }
        }
    }

    pub fn prev(&self, term: &Term, field_name: &str, position: Option<(u64, u32)>) -> Option<(u64, u32)> {
        let posting_fields = match self.index.get(term) {
            Some(posting_fields) => posting_fields,
            None => return None,
        };

        let postings = match posting_fields.get(field_name) {
            Some(postings) => postings,
            None => return None,
        };

        match position {
            Some(position) => {
                // Find first posting before specified position
                // TODO: Speed this up (see section 2.1.2 of the IR book)
                for posting in postings.iter().rev() {
                    if *posting < position {
                        return Some(posting.clone());
                    }
                }

                // Ran out of postings
                return None;
            }
            None => {
                // Position not specified, return last posting
                return postings.last().cloned();
            }
        }
    }

    pub fn initialise(&mut self) {}
}
<|endoftext|>


<commit_before>use std::collections::VecDeque;
use std::net::Ipv6Addr;

use fcp_cryptoauth::{publickey_to_ipv6addr, PublicKey};

use operation::{BackwardPath, Director, ForwardPath};
use packets::control::ControlPacket;
use packets::data::DataPacket;
use packets::data::Payload as DataPayload;
use packets::data::DATAPACKET_VERSION;
use packets::route::RoutePacket;
use packets::switch::Payload as SwitchPayload;
use packets::switch::SwitchPacket;
use passive_switch::PassiveSwitch;
use session_manager::MySessionHandle;
use session_manager::SessionManager;
use utils::new_from_raw_content;

pub struct Ip6Content {
    pub addr: Ipv6Addr,
    pub next_header: u8,
    pub content: Vec<u8>,
}

pub trait RouterTrait {
    /// Called when a RoutePacket is received from the network.
    /// Optionally returns RoutePackets to send back.
    fn on_route_packet(
        &mut self,
        packet: &RoutePacket,
        path: BackwardPath,
        handle: MySessionHandle,
        pk: PublicKey,
    ) -> Vec<RoutePacket>;
}

pub trait NetworkAdapterTrait {
    fn send_to(&mut self, to: Director, packet: &SwitchPacket);
    fn recv_from(&mut self) -> Option<(Director, Vec<SwitchPacket>)>;

    fn directors(&self) -> Vec<Director>;
    fn get_pk(&self, dir: Director) -> Option<&PublicKey>;
}

pub struct Plumbing<Router: RouterTrait, NetworkAdapter: NetworkAdapterTrait> {
    pub router: Router,
    pub network_adapter: NetworkAdapter,
    pub switch: PassiveSwitch,
    pub session_manager: SessionManager,
    /// If not `None`, holds a list of the opaque data received in Pong
    /// packets. Use only when debugging, as it is a DoS vulnerability.
    pub pongs: Option<VecDeque<Vec<u8>>>,
    /// Received "content" packets.
    pub rx_buffer: VecDeque<Ip6Content>,
}

impl<Router: RouterTrait, NetworkAdapter: NetworkAdapterTrait> Plumbing<Router, NetworkAdapter> {
    fn on_control_packet(&mut self, packet: ControlPacket, path: BackwardPath) {
        match packet {
            ControlPacket::Ping { opaque_data, .. } => {
                // If it is a ping packet, just reply to it.
                let control_response = ControlPacket::Pong {
                    version: 18,
                    opaque_data: opaque_data,
                };
                let packet_response =
                    SwitchPacket::new(path.reverse(), SwitchPayload::Control(control_response));
                self.dispatch(packet_response, 0b001);
            }
            ControlPacket::Pong { opaque_data, .. } => {
                if let Some(ref mut pongs) = self.pongs {
                    pongs.push_back(opaque_data);
                }
            }
            _ => panic!("Can only handle Pings and Pongs."),
        }
    }

    /// Called when a switch packet is sent to the self interface
    fn on_self_interface_switch_packet(
        &mut self,
        switch_packet: &SwitchPacket,
    ) -> Option<(MySessionHandle, Vec<DataPacket>)> {
        match switch_packet.payload() {
            Some(SwitchPayload::Control(control_packet)) => {
                self.on_control_packet(control_packet, switch_packet.label().into());
                None
            }
            Some(SwitchPayload::CryptoAuthHello(handshake)) => {
                // If it is a CryptoAuth handshake Hello packet (ie. if someone is
                // connecting to us), create a new session for this node.
                let (handle, inner_packet) =
                    self.session_manager.on_hello(handshake, switch_packet);
                if inner_packet.len() > 0 {
                    let inner_packet = DataPacket::new_from_raw(inner_packet)
                        .expect("Could not decode data packet");
                    Some((handle, vec![inner_packet]))
                } else {
                    Some((handle, vec![]))
                }
            }
            Some(SwitchPayload::CryptoAuthKey(handshake)) => {
                // If it is a CryptoAuth handshake Key packet (ie. if someone is
                // replies to our connection attempt), find its session and
                // update it.
                Some(self.session_manager.on_key(handshake, switch_packet))
            }
            Some(SwitchPayload::CryptoAuthData(handle, ca_message)) => {
                // If it is a CryptoAuth data packet, first read the session
                // handle to know which CryptoAuth session to use to
                // decrypt it.
                let handle = MySessionHandle(handle);
                let inner_packets = self.session_manager.unwrap_message(handle, ca_message);
                Some((handle, inner_packets))
            }
            _ => panic!("Can only handle Pings, Pongs, and CA."),
        }
    }

    /// Called when a CryptoAuth-wrapped message is received through an end-to-end
    /// session.
    fn on_data_packet(
        &mut self,
        data_packet: &DataPacket,
        handle: MySessionHandle,
        path: BackwardPath,
    ) {
        let mut responses = Vec::new();
        {
            let session = self.session_manager.get_session(handle).unwrap();

            let route_packets = match data_packet.payload().unwrap() {
                DataPayload::Ip6Content(next_header, ref content) => {
                    let their_pk = session.conn.their_pk();
                    let their_ipv6_addr = publickey_to_ipv6addr(their_pk);
                    self.rx_buffer.push_back(Ip6Content {
                        addr: their_ipv6_addr,
                        next_header,
                        content: content.clone(), // TODO: do not clone
                    });
                    Vec::new()
                }
                DataPayload::RoutePacket(route_packet) => self.router.on_route_packet(
                    &route_packet,
                    path,
                    handle,
                    session.conn.their_pk().clone(),
                ),
            };
            for route_packet in route_packets.into_iter() {
                responses.extend(session.wrap_route_packet(path.reverse(), route_packet));
            }
        }
        for response in responses {
            self.dispatch(response, 0b001);
        }
    }

    pub fn dispatch(
        &mut self,
        packet: SwitchPacket,
        from_interface: Director,
    ) -> Option<(MySessionHandle, Vec<DataPacket>)> {
        let (to_self, forward) = self.switch.forward(packet, from_interface);
        for (interface, packet) in forward {
            self.network_adapter.send_to(interface, &packet)
        }

        if let Some(packet) = to_self.as_ref() {
            let switched_path = BackwardPath(packet.label());

            if let Some((handle, packets)) = self.on_self_interface_switch_packet(&packet) {
                for packet in packets.iter() {
                    self.on_data_packet(&packet, handle, switched_path)
                }
                Some((handle, packets))
            } else {
                None
            }
        } else {
            None
        }
    }

    pub fn upkeep(&mut self) -> Vec<(MySessionHandle, Vec<DataPacket>)> {
        let mut to_self = Vec::new();
        if let Some((director, messages)) = self.network_adapter.recv_from() {
            for message in messages.into_iter() {
                if let Some(pkts) = self.dispatch(message, director) {
                    to_self.push(pkts);
                }
            }
        }
        self.upkeep2();
        to_self
    }

    fn upkeep2(&mut self) {
        for message in self.session_manager.upkeep() {
            let to_self = self.dispatch(message, 0b001);
            assert!(to_self.is_none());
        }
    }

    pub fn send_hello(&mut self, path: ForwardPath, pk: PublicKey) {
        let _my_handle = self.session_manager.add_outgoing(Some(path), pk);
        self.upkeep2();
    }

    pub fn send_content_to_path(
        &mut self,
        path: ForwardPath,
        pk: PublicKey,
        ip6_next_header: u8,
        content: Vec<u8>,
        immediately: bool,
    ) {
        let (their_handle, messages) = {
            let session = self.session_manager.get_or_make_session_for_pk(pk);
            let data_payload = DataPayload::Ip6Content(ip6_next_header, content);
            let data_packet = DataPacket::new(DATAPACKET_VERSION, &data_payload);
            let messages = if immediately {
                session.conn.wrap_message_immediately(&data_packet.raw())
            } else {
                session.conn.wrap_message(&data_packet.raw())
            };
            (session.their_handle(), messages)
        };

        for message in messages {
            let switch_packet = new_from_raw_content(path, message, their_handle);
            println!("{:?}", switch_packet);
            self.dispatch(switch_packet, 0b001);
        }
    }

    pub fn send_content(&mut self, _ip6_content: &Ip6Content) {
        unimplemented!("send_content");
    }
}
<commit_msg>Remove print.<commit_after>use std::collections::VecDeque;
use std::net::Ipv6Addr;

use fcp_cryptoauth::{publickey_to_ipv6addr, PublicKey};

use operation::{BackwardPath, Director, ForwardPath};
use packets::control::ControlPacket;
use packets::data::DataPacket;
use packets::data::Payload as DataPayload;
use packets::data::DATAPACKET_VERSION;
use packets::route::RoutePacket;
use packets::switch::Payload as SwitchPayload;
use packets::switch::SwitchPacket;
use passive_switch::PassiveSwitch;
use session_manager::MySessionHandle;
use session_manager::SessionManager;
use utils::new_from_raw_content;

pub struct Ip6Content {
    pub addr: Ipv6Addr,
    pub next_header: u8,
    pub content: Vec<u8>,
}

pub trait RouterTrait {
    /// Called when a RoutePacket is received from the network.
    /// Optionally returns RoutePackets to send back.
    fn on_route_packet(
        &mut self,
        packet: &RoutePacket,
        path: BackwardPath,
        handle: MySessionHandle,
        pk: PublicKey,
    ) -> Vec<RoutePacket>;
}

pub trait NetworkAdapterTrait {
    fn send_to(&mut self, to: Director, packet: &SwitchPacket);
    fn recv_from(&mut self) -> Option<(Director, Vec<SwitchPacket>)>;

    fn directors(&self) -> Vec<Director>;
    fn get_pk(&self, dir: Director) -> Option<&PublicKey>;
}

pub struct Plumbing<Router: RouterTrait, NetworkAdapter: NetworkAdapterTrait> {
    pub router: Router,
    pub network_adapter: NetworkAdapter,
    pub switch: PassiveSwitch,
    pub session_manager: SessionManager,
    /// If not `None`, holds a list of the opaque data received in Pong
    /// packets. Use only when debugging, as it is a DoS vulnerability.
    pub pongs: Option<VecDeque<Vec<u8>>>,
    /// Received "content" packets.
    pub rx_buffer: VecDeque<Ip6Content>,
}

impl<Router: RouterTrait, NetworkAdapter: NetworkAdapterTrait> Plumbing<Router, NetworkAdapter> {
    fn on_control_packet(&mut self, packet: ControlPacket, path: BackwardPath) {
        match packet {
            ControlPacket::Ping { opaque_data, .. } => {
                // If it is a ping packet, just reply to it.
                let control_response = ControlPacket::Pong {
                    version: 18,
                    opaque_data: opaque_data,
                };
                let packet_response =
                    SwitchPacket::new(path.reverse(), SwitchPayload::Control(control_response));
                self.dispatch(packet_response, 0b001);
            }
            ControlPacket::Pong { opaque_data, .. } => {
                if let Some(ref mut pongs) = self.pongs {
                    pongs.push_back(opaque_data);
                }
            }
            _ => panic!("Can only handle Pings and Pongs."),
        }
    }

    /// Called when a switch packet is sent to the self interface
    fn on_self_interface_switch_packet(
        &mut self,
        switch_packet: &SwitchPacket,
    ) -> Option<(MySessionHandle, Vec<DataPacket>)> {
        match switch_packet.payload() {
            Some(SwitchPayload::Control(control_packet)) => {
                self.on_control_packet(control_packet, switch_packet.label().into());
                None
            }
            Some(SwitchPayload::CryptoAuthHello(handshake)) => {
                // If it is a CryptoAuth handshake Hello packet (ie. if someone is
                // connecting to us), create a new session for this node.
                let (handle, inner_packet) =
                    self.session_manager.on_hello(handshake, switch_packet);
                if inner_packet.len() > 0 {
                    let inner_packet = DataPacket::new_from_raw(inner_packet)
                        .expect("Could not decode data packet");
                    Some((handle, vec![inner_packet]))
                } else {
                    Some((handle, vec![]))
                }
            }
            Some(SwitchPayload::CryptoAuthKey(handshake)) => {
                // If it is a CryptoAuth handshake Key packet (ie. if someone is
                // replies to our connection attempt), find its session and
                // update it.
                Some(self.session_manager.on_key(handshake, switch_packet))
            }
            Some(SwitchPayload::CryptoAuthData(handle, ca_message)) => {
                // If it is a CryptoAuth data packet, first read the session
                // handle to know which CryptoAuth session to use to
                // decrypt it.
                let handle = MySessionHandle(handle);
                let inner_packets = self.session_manager.unwrap_message(handle, ca_message);
                Some((handle, inner_packets))
            }
            _ => panic!("Can only handle Pings, Pongs, and CA."),
        }
    }

    /// Called when a CryptoAuth-wrapped message is received through an end-to-end
    /// session.
    fn on_data_packet(
        &mut self,
        data_packet: &DataPacket,
        handle: MySessionHandle,
        path: BackwardPath,
    ) {
        let mut responses = Vec::new();
        {
            let session = self.session_manager.get_session(handle).unwrap();

            let route_packets = match data_packet.payload().unwrap() {
                DataPayload::Ip6Content(next_header, ref content) => {
                    let their_pk = session.conn.their_pk();
                    let their_ipv6_addr = publickey_to_ipv6addr(their_pk);
                    self.rx_buffer.push_back(Ip6Content {
                        addr: their_ipv6_addr,
                        next_header,
                        content: content.clone(), // TODO: do not clone
                    });
                    Vec::new()
                }
                DataPayload::RoutePacket(route_packet) => self.router.on_route_packet(
                    &route_packet,
                    path,
                    handle,
                    session.conn.their_pk().clone(),
                ),
            };
            for route_packet in route_packets.into_iter() {
                responses.extend(session.wrap_route_packet(path.reverse(), route_packet));
            }
        }
        for response in responses {
            self.dispatch(response, 0b001);
        }
    }

    pub fn dispatch(
        &mut self,
        packet: SwitchPacket,
        from_interface: Director,
    ) -> Option<(MySessionHandle, Vec<DataPacket>)> {
        let (to_self, forward) = self.switch.forward(packet, from_interface);
        for (interface, packet) in forward {
            self.network_adapter.send_to(interface, &packet)
        }

        if let Some(packet) = to_self.as_ref() {
            let switched_path = BackwardPath(packet.label());

            if let Some((handle, packets)) = self.on_self_interface_switch_packet(&packet) {
                for packet in packets.iter() {
                    self.on_data_packet(&packet, handle, switched_path)
                }
                Some((handle, packets))
            } else {
                None
            }
        } else {
            None
        }
    }

    pub fn upkeep(&mut self) -> Vec<(MySessionHandle, Vec<DataPacket>)> {
        let mut to_self = Vec::new();
        if let Some((director, messages)) = self.network_adapter.recv_from() {
            for message in messages.into_iter() {
                if let Some(pkts) = self.dispatch(message, director) {
                    to_self.push(pkts);
                }
            }
        }
        self.upkeep2();
        to_self
    }

    fn upkeep2(&mut self) {
        for message in self.session_manager.upkeep() {
            let to_self = self.dispatch(message, 0b001);
            assert!(to_self.is_none());
        }
    }

    pub fn send_hello(&mut self, path: ForwardPath, pk: PublicKey) {
        let _my_handle = self.session_manager.add_outgoing(Some(path), pk);
        self.upkeep2();
    }

    pub fn send_content_to_path(
        &mut self,
        path: ForwardPath,
        pk: PublicKey,
        ip6_next_header: u8,
        content: Vec<u8>,
        immediately: bool,
    ) {
        let (their_handle, messages) = {
            let session = self.session_manager.get_or_make_session_for_pk(pk);
            let data_payload = DataPayload::Ip6Content(ip6_next_header, content);
            let data_packet = DataPacket::new(DATAPACKET_VERSION, &data_payload);
            let messages = if immediately {
                session.conn.wrap_message_immediately(&data_packet.raw())
            } else {
                session.conn.wrap_message(&data_packet.raw())
            };
            (session.their_handle(), messages)
        };

        for message in messages {
            let switch_packet = new_from_raw_content(path, message, their_handle);
            self.dispatch(switch_packet, 0b001);
        }
    }

    pub fn send_content(&mut self, _ip6_content: &Ip6Content) {
        unimplemented!("send_content");
    }
}
<|endoftext|>


<commit_before>use ffi::h5::hsize_t;
use ffi::h5i::{H5I_DATASPACE, hid_t};
use ffi::h5s::{H5S_UNLIMITED, H5Sget_simple_extent_dims, H5Sget_simple_extent_ndims,
               H5Screate_simple};

use error::Result;
use handle::{Handle, ID, get_id_type};
use object::Object;

use std::ptr;
use libc::c_int;

pub type Ix = usize;

pub trait Dimension {
    fn ndim(&self) -> usize;
    fn dims(&self) -> Vec<Ix>;

    fn size(&self) -> usize {
        let dims = self.dims();
        if dims.is_empty() { 0 } else { dims.iter().fold(1, |acc, &el| acc * el) }
    }
}

impl Dimension for () {
    fn ndim(&self) -> usize { 0 }
    fn dims(&self) -> Vec<Ix> { vec![] }
}

impl Dimension for Ix {
    fn ndim(&self) -> usize { 1 }
    fn dims(&self) -> Vec<Ix> { vec![*self] }
}

impl Dimension for (Ix,) {
    fn ndim(&self) -> usize { 1 }
    fn dims(&self) -> Vec<Ix> { vec![self.0] }
}

impl Dimension for (Ix, Ix) {
    fn ndim(&self) -> usize { 2 }
    fn dims(&self) -> Vec<Ix> { vec![self.0, self.1] }
}

pub struct Dataspace {
    handle: Handle,
}

impl Dataspace {
    pub fn new<D: Dimension>(d: D) -> Result<Dataspace> {
        let rank = d.ndim();
        let mut dims: Vec<hsize_t> = vec![];
        let mut max_dims: Vec<hsize_t> = vec![];
        for dim in d.dims().iter() {
            dims.push(*dim as hsize_t);
            max_dims.push(H5S_UNLIMITED);
        }
        Dataspace::from_id(h5try!(H5Screate_simple(rank as c_int, dims.as_ptr(),
                                                   max_dims.as_ptr())))
    }
}

impl Dimension for Dataspace {
    fn ndim(&self) -> usize {
        h5call!(H5Sget_simple_extent_ndims(self.id())).unwrap_or(0) as usize
    }

    fn dims(&self) -> Vec<Ix> {
        let ndim = self.ndim();
        if ndim > 0 {
            let mut dims: Vec<hsize_t> = Vec::with_capacity(ndim);
            unsafe { dims.set_len(ndim); }
            if h5call!(H5Sget_simple_extent_dims(self.id(), dims.as_mut_ptr(),
                                                 ptr::null_mut())).is_ok() {
                return dims.iter().cloned().map(|x| x as usize).collect();
            }
        }
        vec![]
    }
}

impl ID for Dataspace {
    fn id(&self) -> hid_t {
        self.handle.id()
    }

    fn from_id(id: hid_t) -> Result<Dataspace> {
        match get_id_type(id) {
            H5I_DATASPACE => Ok(Dataspace { handle: try!(Handle::new(id)) }),
            _             => Err(From::from(format!("Invalid dataspace id: {}", id))),
        }
    }
}

impl Object for Dataspace {}
<commit_msg>Implement Dimension for Vec and all associated reference types<commit_after>use ffi::h5::hsize_t;
use ffi::h5i::{H5I_DATASPACE, hid_t};
use ffi::h5s::{H5S_UNLIMITED, H5Sget_simple_extent_dims, H5Sget_simple_extent_ndims,
               H5Screate_simple};

use error::Result;
use handle::{Handle, ID, get_id_type};
use object::Object;

use std::ptr;
use libc::c_int;

pub type Ix = usize;

pub trait Dimension {
    fn ndim(&self) -> usize;
    fn dims(&self) -> Vec<Ix>;

    fn size(&self) -> usize {
        let dims = self.dims();
        if dims.is_empty() { 0 } else { dims.iter().fold(1, |acc, &el| acc * el) }
    }
}

impl<'a, T: Dimension> Dimension for &'a T {
    fn ndim(&self) -> usize { Dimension::ndim(*self) }
    fn dims(&self) -> Vec<Ix> { Dimension::dims(*self) }
}

impl Dimension for Vec<Ix> {
    fn ndim(&self) -> usize { self.len() }
    fn dims(&self) -> Vec<Ix> { self.clone() }
}

impl Dimension for () {
    fn ndim(&self) -> usize { 0 }
    fn dims(&self) -> Vec<Ix> { vec![] }
}

impl Dimension for Ix {
    fn ndim(&self) -> usize { 1 }
    fn dims(&self) -> Vec<Ix> { vec![*self] }
}

impl Dimension for (Ix,) {
    fn ndim(&self) -> usize { 1 }
    fn dims(&self) -> Vec<Ix> { vec![self.0] }
}

impl Dimension for (Ix, Ix) {
    fn ndim(&self) -> usize { 2 }
    fn dims(&self) -> Vec<Ix> { vec![self.0, self.1] }
}

pub struct Dataspace {
    handle: Handle,
}

impl Dataspace {
    pub fn new<D: Dimension>(d: D) -> Result<Dataspace> {
        let rank = d.ndim();
        let mut dims: Vec<hsize_t> = vec![];
        let mut max_dims: Vec<hsize_t> = vec![];
        for dim in d.dims().iter() {
            dims.push(*dim as hsize_t);
            max_dims.push(H5S_UNLIMITED);
        }
        Dataspace::from_id(h5try!(H5Screate_simple(rank as c_int, dims.as_ptr(),
                                                   max_dims.as_ptr())))
    }
}

impl Dimension for Dataspace {
    fn ndim(&self) -> usize {
        h5call!(H5Sget_simple_extent_ndims(self.id())).unwrap_or(0) as usize
    }

    fn dims(&self) -> Vec<Ix> {
        let ndim = self.ndim();
        if ndim > 0 {
            let mut dims: Vec<hsize_t> = Vec::with_capacity(ndim);
            unsafe { dims.set_len(ndim); }
            if h5call!(H5Sget_simple_extent_dims(self.id(), dims.as_mut_ptr(),
                                                 ptr::null_mut())).is_ok() {
                return dims.iter().cloned().map(|x| x as usize).collect();
            }
        }
        vec![]
    }
}

impl ID for Dataspace {
    fn id(&self) -> hid_t {
        self.handle.id()
    }

    fn from_id(id: hid_t) -> Result<Dataspace> {
        match get_id_type(id) {
            H5I_DATASPACE => Ok(Dataspace { handle: try!(Handle::new(id)) }),
            _             => Err(From::from(format!("Invalid dataspace id: {}", id))),
        }
    }
}

impl Object for Dataspace {}
<|endoftext|>