<commit_before>//! Contains the binary logic of Ion.
pub mod builtins;
mod completer;
mod designators;
mod history;
mod lexer;
mod prompt;
mod readln;

use ion_shell::{
    builtins::{man_pages, Status},
    expansion::Expander,
    parser::Terminator,
    types, Capture, IonError, PipelineError, Shell, Signal,
};
use itertools::Itertools;
use liner::{Buffer, Context, KeyBindings};
use std::{
    cell::RefCell,
    fs::{self, OpenOptions},
    io,
    io::Write,
    path::Path,
    rc::Rc,
};
use xdg::BaseDirectories;

#[cfg(not(feature = "advanced_arg_parsing"))]
pub const MAN_ION: &str = r#"Ion - The Ion Shell 1.0.0-alpha
Ion is a commandline shell created to be a faster and easier to use alternative to the currently available shells. It is
not POSIX compliant.

USAGE:
    ion [FLAGS] [OPTIONS] [args]...

FLAGS:
    -h, --help           Prints help information
    -i, --interactive    Force interactive mode
    -n, --no-execute     Do not execute any commands, perform only syntax checking
    -x                   Print commands before execution
    -v, --version        Print the version, platform and revision of Ion then exit

OPTIONS:
    -c <command>             Evaluate given commands instead of reading from the commandline
    -o <key_bindings>        Shortcut layout. Valid options: "vi", "emacs"

ARGS:
    <args>...    Script arguments (@args). If the -c option is not specified, the first parameter is taken as a
                 filename to execute"#;

pub(crate) const MAN_HISTORY: &str = r#"NAME
    history - print command history

SYNOPSIS
    history [option]

DESCRIPTION
    Prints or manupulate the command history.

OPTIONS:
    +inc_append: Append each command to history as entered.
    -inc_append: Default, do not append each command to history as entered.
    +shared: Share history between shells using the same history file, implies inc_append.
    -shared: Default, do not share shell history.
    +duplicates: Default, allow duplicates in history.
    -duplicates: Do not allow duplicates in history.
"#;

pub struct InteractiveShell<'a> {
    context: Rc<RefCell<Context>>,
    shell:   RefCell<Shell<'a>>,
}

impl<'a> InteractiveShell<'a> {
    const CONFIG_FILE_NAME: &'static str = "initrc";

    pub fn new(shell: Shell<'a>) -> Self {
        let mut context = Context::new();
        context.word_divider_fn = Box::new(word_divide);
        if shell.variables().get_str("HISTFILE_ENABLED").ok() == Some("1".into()) {
            let path = shell.variables().get_str("HISTFILE").expect("shell didn't set HISTFILE");
            if !Path::new(path.as_str()).exists() {
                eprintln!("ion: creating history file at \"{}\"", path);
            }
            let _ = context.history.set_file_name_and_load_history(path.as_str());
        }
        InteractiveShell { context: Rc::new(RefCell::new(context)), shell: RefCell::new(shell) }
    }

    /// Handles commands given by the REPL, and saves them to history.
    pub fn save_command(&self, cmd: &str) {
        if !cmd.ends_with('/')
            && self
                .shell
                .borrow()
                .tilde(cmd)
                .ok()
                .map_or(false, |path| Path::new(&path.as_str()).is_dir())
        {
            self.save_command_in_history(&[cmd, "/"].concat());
        } else {
            self.save_command_in_history(cmd);
        }
    }

    pub fn add_callbacks(&self) {
        let context = self.context.clone();
        self.shell.borrow_mut().set_on_command(Some(Box::new(move |shell, elapsed| {
            // If `RECORD_SUMMARY` is set to "1" (True, Yes), then write a summary of the
            // pipline just executed to the the file and context histories. At the
            // moment, this means record how long it took.
            if Some("1".into()) == shell.variables().get_str("RECORD_SUMMARY").ok() {
                let summary = format!(
                    "#summary# elapsed real time: {}.{:09} seconds",
                    elapsed.as_secs(),
                    elapsed.subsec_nanos()
                );
                println!("{:?}", summary);
                context.borrow_mut().history.push(summary.into()).unwrap_or_else(|err| {
                    eprintln!("ion: history append: {}", err);
                });
            }
        })));
    }

    fn create_config_file(base_dirs: BaseDirectories, file_name: &str) -> Result<(), io::Error> {
        let path = base_dirs.place_config_file(file_name)?;
        OpenOptions::new().write(true).create_new(true).open(path)?;
        Ok(())
    }

    /// Creates an interactive session that reads from a prompt provided by
    /// Liner.
    pub fn execute_interactive(self) -> ! {
        let context_bis = self.context.clone();
        let prep_for_exit = &move |shell: &mut Shell<'_>| {
            // context will be sent a signal to commit all changes to the history file,
            // and waiting for the history thread in the background to finish.
            if shell.opts().huponexit {
                shell.resume_stopped();
                shell.background_send(Signal::SIGHUP).expect("Failed to prepare for exit");
            }
            context_bis.borrow_mut().history.commit_to_file();
        };

        let exit = self.shell.borrow().builtins().get("exit").unwrap();
        let exit = &|args: &[types::Str], shell: &mut Shell<'_>| -> Status {
            prep_for_exit(shell);
            exit(args, shell)
        };

        let exec = self.shell.borrow().builtins().get("exec").unwrap();
        let exec = &|args: &[types::Str], shell: &mut Shell<'_>| -> Status {
            prep_for_exit(shell);
            exec(args, shell)
        };

        let context_bis = self.context.clone();
        let history = &move |args: &[types::Str], _shell: &mut Shell<'_>| -> Status {
            if man_pages::check_help(args, MAN_HISTORY) {
                return Status::SUCCESS;
            }

            match args.get(1).map(|s| s.as_str()) {
                Some("+inc_append") => {
                    context_bis.borrow_mut().history.inc_append = true;
                }
                Some("-inc_append") => {
                    context_bis.borrow_mut().history.inc_append = false;
                }
                Some("+share") => {
                    context_bis.borrow_mut().history.inc_append = true;
                    context_bis.borrow_mut().history.share = true;
                }
                Some("-share") => {
                    context_bis.borrow_mut().history.inc_append = false;
                    context_bis.borrow_mut().history.share = false;
                }
                Some("+duplicates") => {
                    context_bis.borrow_mut().history.load_duplicates = true;
                }
                Some("-duplicates") => {
                    context_bis.borrow_mut().history.load_duplicates = false;
                }
                Some(_) => {
                    Status::error("Invalid history option. Choices are [+|-] inc_append and share (implies inc_append).");
                }
                None => {
                    print!("{}", context_bis.borrow().history.buffers.iter().format("\n"));
                    io::stdout().flush().unwrap_or_else(|err| {
                        eprintln!("ion: history print: {}", err);
                    });
                }
            }
            Status::SUCCESS
        };

        let context_bis = self.context.clone();
        let keybindings = &move |args: &[types::Str], _shell: &mut Shell<'_>| -> Status {
            match args.get(1).map(|s| s.as_str()) {
                Some("vi") => {
                    context_bis.borrow_mut().key_bindings = KeyBindings::Vi;
                    Status::SUCCESS
                }
                Some("emacs") => {
                    context_bis.borrow_mut().key_bindings = KeyBindings::Emacs;
                    Status::SUCCESS
                }
                Some(_) => Status::error("Invalid keybindings. Choices are vi and emacs"),
                None => Status::error("keybindings need an argument"),
            }
        };

        // change the lifetime to allow adding local builtins
        let InteractiveShell { context, shell } = self;
        let mut shell = shell.into_inner();
        shell
            .builtins_mut()
            .add("history", history, "Display a log of all commands previously executed")
            .add("keybindings", keybindings, "Change the keybindings")
            .add("exit", exit, "Exits the current session")
            .add("exec", exec, "Replace the shell with the given command.");

        Self::exec_init_file(&mut shell);

        InteractiveShell { context, shell: RefCell::new(shell) }.exec(prep_for_exit)
    }

    fn exec_init_file(shell: &mut Shell) {
        match BaseDirectories::with_prefix("ion") {
            Ok(base_dirs) => match base_dirs.find_config_file(Self::CONFIG_FILE_NAME) {
                Some(initrc) => match fs::File::open(initrc) {
                    Ok(script) => {
                        if let Err(err) = shell.execute_command(std::io::BufReader::new(script)) {
                            eprintln!("ion: {}", err);
                        }
                    }
                    Err(cause) => println!("ion: init file was not found: {}", cause),
                },
                None => {
                    if let Err(err) = Self::create_config_file(base_dirs, Self::CONFIG_FILE_NAME) {
                        eprintln!("ion: could not create config file: {}", err);
                    }
                }
            },
            Err(err) => {
                eprintln!("ion: unable to get base directory: {}", err);
            }
        }
    }

    fn exec<T: Fn(&mut Shell<'_>)>(self, prep_for_exit: &T) -> ! {
        loop {
            let mut lines = std::iter::repeat_with(|| self.readln(prep_for_exit))
                .filter_map(|cmd| cmd)
                .flat_map(|s| s.into_bytes().into_iter().chain(Some(b'\n')));
            match Terminator::new(&mut lines).terminate() {
                Some(command) => {
                    let cmd: &str = &designators::expand_designators(
                        &self.context.borrow(),
                        command.trim_end(),
                    );
                    {
                        let mut shell = self.shell.borrow_mut();
                        shell.unterminated = false;
                        match shell.on_command(&cmd) {
                            Ok(_) => (),
                            Err(IonError::PipelineExecutionError(
                                PipelineError::CommandNotFound(command),
                            )) => {
                                if shell
                                    .fork_function(
                                        Capture::None,
                                        |_| Ok(()),
                                        "COMMAND_NOT_FOUND",
                                        &["ion", &command],
                                    )
                                    .is_err()
                                {
                                    eprintln!("ion: command not found: {}", command);
                                }
                                // Status::COULD_NOT_EXEC
                            }
                            Err(err) => {
                                eprintln!("ion: {}", err);
                                shell.reset_flow();
                            }
                        }
                    }
                    self.save_command(&cmd);
                }
                None => {
                    self.shell.borrow_mut().unterminated = true;
                }
            }
        }
    }

    /// Set the keybindings of the underlying liner context
    pub fn set_keybindings(&mut self, key_bindings: KeyBindings) {
        self.context.borrow_mut().key_bindings = key_bindings;
    }
}

#[derive(Debug)]
struct WordDivide<I>
where
    I: Iterator<Item = (usize, char)>,
{
    iter:       I,
    count:      usize,
    word_start: Option<usize>,
}
impl<I> WordDivide<I>
where
    I: Iterator<Item = (usize, char)>,
{
    #[inline]
    fn check_boundary(&mut self, c: char, index: usize, escaped: bool) -> Option<(usize, usize)> {
        if let Some(start) = self.word_start {
            if c == ' ' && !escaped {
                self.word_start = None;
                Some((start, index))
            } else {
                self.next()
            }
        } else {
            if c != ' ' {
                self.word_start = Some(index);
            }
            self.next()
        }
    }
}
impl<I> Iterator for WordDivide<I>
where
    I: Iterator<Item = (usize, char)>,
{
    type Item = (usize, usize);

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        match self.iter.next() {
            Some((i, '\\')) => {
                if let Some((_, cnext)) = self.iter.next() {
                    self.count += 1;
                    // We use `i` in order to include the backslash as part of the word
                    self.check_boundary(cnext, i, true)
                } else {
                    self.next()
                }
            }
            Some((i, c)) => self.check_boundary(c, i, false),
            None => {
                // When start has been set, that means we have encountered a full word.
                self.word_start.take().map(|start| (start, self.count - 1))
            }
        }
    }
}

fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> {
    // -> impl Iterator<Item = (usize, usize)> + 'a
    WordDivide { iter: buf.chars().cloned().enumerate(), count: 0, word_start: None }.collect() // TODO: return iterator directly :D
}
<commit_msg>Flush outputs before prompt, maybe not best way to fix this?<commit_after>//! Contains the binary logic of Ion.
pub mod builtins;
mod completer;
mod designators;
mod history;
mod lexer;
mod prompt;
mod readln;

use ion_shell::{
    builtins::{man_pages, Status},
    expansion::Expander,
    parser::Terminator,
    types, Capture, IonError, PipelineError, Shell, Signal,
};
use itertools::Itertools;
use liner::{Buffer, Context, KeyBindings};
use std::{
    cell::RefCell,
    fs::{self, OpenOptions},
    io,
    io::Write,
    path::Path,
    rc::Rc,
};
use xdg::BaseDirectories;

#[cfg(not(feature = "advanced_arg_parsing"))]
pub const MAN_ION: &str = r#"Ion - The Ion Shell 1.0.0-alpha
Ion is a commandline shell created to be a faster and easier to use alternative to the currently available shells. It is
not POSIX compliant.

USAGE:
    ion [FLAGS] [OPTIONS] [args]...

FLAGS:
    -h, --help           Prints help information
    -i, --interactive    Force interactive mode
    -n, --no-execute     Do not execute any commands, perform only syntax checking
    -x                   Print commands before execution
    -v, --version        Print the version, platform and revision of Ion then exit

OPTIONS:
    -c <command>             Evaluate given commands instead of reading from the commandline
    -o <key_bindings>        Shortcut layout. Valid options: "vi", "emacs"

ARGS:
    <args>...    Script arguments (@args). If the -c option is not specified, the first parameter is taken as a
                 filename to execute"#;

pub(crate) const MAN_HISTORY: &str = r#"NAME
    history - print command history

SYNOPSIS
    history [option]

DESCRIPTION
    Prints or manupulate the command history.

OPTIONS:
    +inc_append: Append each command to history as entered.
    -inc_append: Default, do not append each command to history as entered.
    +shared: Share history between shells using the same history file, implies inc_append.
    -shared: Default, do not share shell history.
    +duplicates: Default, allow duplicates in history.
    -duplicates: Do not allow duplicates in history.
"#;

pub struct InteractiveShell<'a> {
    context: Rc<RefCell<Context>>,
    shell:   RefCell<Shell<'a>>,
}

impl<'a> InteractiveShell<'a> {
    const CONFIG_FILE_NAME: &'static str = "initrc";

    pub fn new(shell: Shell<'a>) -> Self {
        let mut context = Context::new();
        context.word_divider_fn = Box::new(word_divide);
        if shell.variables().get_str("HISTFILE_ENABLED").ok() == Some("1".into()) {
            let path = shell.variables().get_str("HISTFILE").expect("shell didn't set HISTFILE");
            if !Path::new(path.as_str()).exists() {
                eprintln!("ion: creating history file at \"{}\"", path);
            }
            let _ = context.history.set_file_name_and_load_history(path.as_str());
        }
        InteractiveShell { context: Rc::new(RefCell::new(context)), shell: RefCell::new(shell) }
    }

    /// Handles commands given by the REPL, and saves them to history.
    pub fn save_command(&self, cmd: &str) {
        if !cmd.ends_with('/')
            && self
                .shell
                .borrow()
                .tilde(cmd)
                .ok()
                .map_or(false, |path| Path::new(&path.as_str()).is_dir())
        {
            self.save_command_in_history(&[cmd, "/"].concat());
        } else {
            self.save_command_in_history(cmd);
        }
    }

    pub fn add_callbacks(&self) {
        let context = self.context.clone();
        self.shell.borrow_mut().set_on_command(Some(Box::new(move |shell, elapsed| {
            // If `RECORD_SUMMARY` is set to "1" (True, Yes), then write a summary of the
            // pipline just executed to the the file and context histories. At the
            // moment, this means record how long it took.
            if Some("1".into()) == shell.variables().get_str("RECORD_SUMMARY").ok() {
                let summary = format!(
                    "#summary# elapsed real time: {}.{:09} seconds",
                    elapsed.as_secs(),
                    elapsed.subsec_nanos()
                );
                println!("{:?}", summary);
                context.borrow_mut().history.push(summary.into()).unwrap_or_else(|err| {
                    eprintln!("ion: history append: {}", err);
                });
            }
        })));
    }

    fn create_config_file(base_dirs: BaseDirectories, file_name: &str) -> Result<(), io::Error> {
        let path = base_dirs.place_config_file(file_name)?;
        OpenOptions::new().write(true).create_new(true).open(path)?;
        Ok(())
    }

    /// Creates an interactive session that reads from a prompt provided by
    /// Liner.
    pub fn execute_interactive(self) -> ! {
        let context_bis = self.context.clone();
        let prep_for_exit = &move |shell: &mut Shell<'_>| {
            // context will be sent a signal to commit all changes to the history file,
            // and waiting for the history thread in the background to finish.
            if shell.opts().huponexit {
                shell.resume_stopped();
                shell.background_send(Signal::SIGHUP).expect("Failed to prepare for exit");
            }
            context_bis.borrow_mut().history.commit_to_file();
        };

        let exit = self.shell.borrow().builtins().get("exit").unwrap();
        let exit = &|args: &[types::Str], shell: &mut Shell<'_>| -> Status {
            prep_for_exit(shell);
            exit(args, shell)
        };

        let exec = self.shell.borrow().builtins().get("exec").unwrap();
        let exec = &|args: &[types::Str], shell: &mut Shell<'_>| -> Status {
            prep_for_exit(shell);
            exec(args, shell)
        };

        let context_bis = self.context.clone();
        let history = &move |args: &[types::Str], _shell: &mut Shell<'_>| -> Status {
            if man_pages::check_help(args, MAN_HISTORY) {
                return Status::SUCCESS;
            }

            match args.get(1).map(|s| s.as_str()) {
                Some("+inc_append") => {
                    context_bis.borrow_mut().history.inc_append = true;
                }
                Some("-inc_append") => {
                    context_bis.borrow_mut().history.inc_append = false;
                }
                Some("+share") => {
                    context_bis.borrow_mut().history.inc_append = true;
                    context_bis.borrow_mut().history.share = true;
                }
                Some("-share") => {
                    context_bis.borrow_mut().history.inc_append = false;
                    context_bis.borrow_mut().history.share = false;
                }
                Some("+duplicates") => {
                    context_bis.borrow_mut().history.load_duplicates = true;
                }
                Some("-duplicates") => {
                    context_bis.borrow_mut().history.load_duplicates = false;
                }
                Some(_) => {
                    Status::error("Invalid history option. Choices are [+|-] inc_append and share (implies inc_append).");
                }
                None => {
                    print!("{}", context_bis.borrow().history.buffers.iter().format("\n"));
                }
            }
            Status::SUCCESS
        };

        let context_bis = self.context.clone();
        let keybindings = &move |args: &[types::Str], _shell: &mut Shell<'_>| -> Status {
            match args.get(1).map(|s| s.as_str()) {
                Some("vi") => {
                    context_bis.borrow_mut().key_bindings = KeyBindings::Vi;
                    Status::SUCCESS
                }
                Some("emacs") => {
                    context_bis.borrow_mut().key_bindings = KeyBindings::Emacs;
                    Status::SUCCESS
                }
                Some(_) => Status::error("Invalid keybindings. Choices are vi and emacs"),
                None => Status::error("keybindings need an argument"),
            }
        };

        // change the lifetime to allow adding local builtins
        let InteractiveShell { context, shell } = self;
        let mut shell = shell.into_inner();
        shell
            .builtins_mut()
            .add("history", history, "Display a log of all commands previously executed")
            .add("keybindings", keybindings, "Change the keybindings")
            .add("exit", exit, "Exits the current session")
            .add("exec", exec, "Replace the shell with the given command.");

        Self::exec_init_file(&mut shell);

        InteractiveShell { context, shell: RefCell::new(shell) }.exec(prep_for_exit)
    }

    fn exec_init_file(shell: &mut Shell) {
        match BaseDirectories::with_prefix("ion") {
            Ok(base_dirs) => match base_dirs.find_config_file(Self::CONFIG_FILE_NAME) {
                Some(initrc) => match fs::File::open(initrc) {
                    Ok(script) => {
                        if let Err(err) = shell.execute_command(std::io::BufReader::new(script)) {
                            eprintln!("ion: {}", err);
                        }
                    }
                    Err(cause) => println!("ion: init file was not found: {}", cause),
                },
                None => {
                    if let Err(err) = Self::create_config_file(base_dirs, Self::CONFIG_FILE_NAME) {
                        eprintln!("ion: could not create config file: {}", err);
                    }
                }
            },
            Err(err) => {
                eprintln!("ion: unable to get base directory: {}", err);
            }
        }
    }

    fn exec<T: Fn(&mut Shell<'_>)>(self, prep_for_exit: &T) -> ! {
        loop {
            io::stdout().flush().unwrap_or_else(|err| {
                eprintln!("ion: failed to flush stdio: {}", err);
            });
            io::stderr().flush().unwrap_or_else(|err| {
                println!("ion: failed to flush stderr: {}", err);
            });
            let mut lines = std::iter::repeat_with(|| self.readln(prep_for_exit))
                .filter_map(|cmd| cmd)
                .flat_map(|s| s.into_bytes().into_iter().chain(Some(b'\n')));
            match Terminator::new(&mut lines).terminate() {
                Some(command) => {
                    let cmd: &str = &designators::expand_designators(
                        &self.context.borrow(),
                        command.trim_end(),
                    );
                    {
                        let mut shell = self.shell.borrow_mut();
                        shell.unterminated = false;
                        match shell.on_command(&cmd) {
                            Ok(_) => (),
                            Err(IonError::PipelineExecutionError(
                                PipelineError::CommandNotFound(command),
                            )) => {
                                if shell
                                    .fork_function(
                                        Capture::None,
                                        |_| Ok(()),
                                        "COMMAND_NOT_FOUND",
                                        &["ion", &command],
                                    )
                                    .is_err()
                                {
                                    eprintln!("ion: command not found: {}", command);
                                }
                                // Status::COULD_NOT_EXEC
                            }
                            Err(err) => {
                                eprintln!("ion: {}", err);
                                shell.reset_flow();
                            }
                        }
                    }
                    self.save_command(&cmd);
                }
                None => {
                    self.shell.borrow_mut().unterminated = true;
                }
            }
        }
    }

    /// Set the keybindings of the underlying liner context
    pub fn set_keybindings(&mut self, key_bindings: KeyBindings) {
        self.context.borrow_mut().key_bindings = key_bindings;
    }
}

#[derive(Debug)]
struct WordDivide<I>
where
    I: Iterator<Item = (usize, char)>,
{
    iter:       I,
    count:      usize,
    word_start: Option<usize>,
}
impl<I> WordDivide<I>
where
    I: Iterator<Item = (usize, char)>,
{
    #[inline]
    fn check_boundary(&mut self, c: char, index: usize, escaped: bool) -> Option<(usize, usize)> {
        if let Some(start) = self.word_start {
            if c == ' ' && !escaped {
                self.word_start = None;
                Some((start, index))
            } else {
                self.next()
            }
        } else {
            if c != ' ' {
                self.word_start = Some(index);
            }
            self.next()
        }
    }
}
impl<I> Iterator for WordDivide<I>
where
    I: Iterator<Item = (usize, char)>,
{
    type Item = (usize, usize);

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        match self.iter.next() {
            Some((i, '\\')) => {
                if let Some((_, cnext)) = self.iter.next() {
                    self.count += 1;
                    // We use `i` in order to include the backslash as part of the word
                    self.check_boundary(cnext, i, true)
                } else {
                    self.next()
                }
            }
            Some((i, c)) => self.check_boundary(c, i, false),
            None => {
                // When start has been set, that means we have encountered a full word.
                self.word_start.take().map(|start| (start, self.count - 1))
            }
        }
    }
}

fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> {
    // -> impl Iterator<Item = (usize, usize)> + 'a
    WordDivide { iter: buf.chars().cloned().enumerate(), count: 0, word_start: None }.collect() // TODO: return iterator directly :D
}
<|endoftext|>


<commit_before>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use hyper::Method;
use futures::future::{self, Future};

use super::check::CacheCheck;

use APP_CONF;
use APP_CACHE_STORE;

pub struct CacheRead;

pub enum CacheReadError {
    PassThrough,
    Empty,
    StoreFailure,
}

type CacheReadResult = Result<(String, String), CacheReadError>;
type CacheReadFuture = Box<Future<Item = CacheReadResult, Error = ()>>;

impl CacheRead {
    pub fn acquire(shard: u8, key: &str, method: &Method) -> CacheReadFuture {
        if APP_CONF.cache.disable_read == false && CacheCheck::from_request(&method) == true {
            debug!("key: {} cacheable, reading cache", &key);

            Box::new(
                APP_CACHE_STORE
                    .get(shard, key.to_string())
                    .and_then(|acquired| if let Some(result) = acquired {
                        future::ok(Ok(result))
                    } else {
                        info!("acquired empty value from cache");

                        future::ok(Err(CacheReadError::Empty))
                    })
                    .or_else(|err| {
                        error!("could not acquire value from cache because: {:?}", err);

                        future::ok(Err(CacheReadError::StoreFailure))
                    }),
            )
        } else {
            debug!("key: {} not cacheable, ignoring (will pass through)", &key);

            Box::new(future::ok(Err(CacheReadError::PassThrough)))
        }
    }
}

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

    #[test]
    #[should_panic]
    fn it_fails_acquiring_cache() {
        assert!(
            CacheRead::acquire("bloom:0:c:90d52bc6:f773d6f1", &Method::Get)
                .poll()
                .is_err()
        );
    }
}
<commit_msg>Fix tests<commit_after>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use hyper::Method;
use futures::future::{self, Future};

use super::check::CacheCheck;

use APP_CONF;
use APP_CACHE_STORE;

pub struct CacheRead;

pub enum CacheReadError {
    PassThrough,
    Empty,
    StoreFailure,
}

type CacheReadResult = Result<(String, String), CacheReadError>;
type CacheReadFuture = Box<Future<Item = CacheReadResult, Error = ()>>;

impl CacheRead {
    pub fn acquire(shard: u8, key: &str, method: &Method) -> CacheReadFuture {
        if APP_CONF.cache.disable_read == false && CacheCheck::from_request(&method) == true {
            debug!("key: {} cacheable, reading cache", &key);

            Box::new(
                APP_CACHE_STORE
                    .get(shard, key.to_string())
                    .and_then(|acquired| if let Some(result) = acquired {
                        future::ok(Ok(result))
                    } else {
                        info!("acquired empty value from cache");

                        future::ok(Err(CacheReadError::Empty))
                    })
                    .or_else(|err| {
                        error!("could not acquire value from cache because: {:?}", err);

                        future::ok(Err(CacheReadError::StoreFailure))
                    }),
            )
        } else {
            debug!("key: {} not cacheable, ignoring (will pass through)", &key);

            Box::new(future::ok(Err(CacheReadError::PassThrough)))
        }
    }
}

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

    #[test]
    #[should_panic]
    fn it_fails_acquiring_cache() {
        assert!(
            CacheRead::acquire(0, "bloom:0:c:90d52bc6:f773d6f1", &Method::Get)
                .poll()
                .is_err()
        );
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! This module is responsible to build a single project. It does not handle
//! watch mode or other state.

mod apply_transforms;
mod build_ir;
mod build_schema;

use crate::compiler_state::{CompilerState, SourceSetName};
use crate::config::ConfigProject;
use common::Timer;
use graphql_ir::{Program, ValidationError};
use std::collections::HashMap;

pub fn build_project(
    compiler_state: &CompilerState,
    project_config: &ConfigProject,
    ast_sets: &HashMap<SourceSetName, Vec<graphql_syntax::ExecutableDefinition>>,
) -> std::result::Result<(), Vec<ValidationError>> {
    let build_schema_timer = Timer::start(format!("build_schema {}", project_config.name));
    let schema = build_schema::build_schema(compiler_state, project_config);
    build_schema_timer.stop();

    let ir = Timer::time(format!("build_ir {}", project_config.name), || {
        build_ir::build_ir(project_config, &schema, ast_sets)
    })?;

    let program = Timer::time(format!("build_program {}", project_config.name), || {
        Program::from_definitions(&schema, ir)
    });

    let programs = Timer::time(format!("apply_transforms {}", project_config.name), || {
        apply_transforms::apply_transforms(&program)
    });

    println!(
        "[{}] documents: {} reader, {} normalization, {} operation",
        project_config.name,
        programs.reader.document_count(),
        programs.normalization.document_count(),
        programs.operation_text.document_count()
    );

    Ok(())
}
<commit_msg>missed comment from D20122132<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! This module is responsible to build a single project. It does not handle
//! watch mode or other state.

mod apply_transforms;
mod build_ir;
mod build_schema;

use crate::compiler_state::{CompilerState, SourceSetName};
use crate::config::ConfigProject;
use common::Timer;
use graphql_ir::{Program, ValidationError};
use std::collections::HashMap;

pub fn build_project(
    compiler_state: &CompilerState,
    project_config: &ConfigProject,
    ast_sets: &HashMap<SourceSetName, Vec<graphql_syntax::ExecutableDefinition>>,
) -> std::result::Result<(), Vec<ValidationError>> {
    let schema = Timer::time(format!("build_schema {}", project_config.name), || {
        build_schema::build_schema(compiler_state, project_config)
    });

    let ir = Timer::time(format!("build_ir {}", project_config.name), || {
        build_ir::build_ir(project_config, &schema, ast_sets)
    })?;

    let program = Timer::time(format!("build_program {}", project_config.name), || {
        Program::from_definitions(&schema, ir)
    });

    let programs = Timer::time(format!("apply_transforms {}", project_config.name), || {
        apply_transforms::apply_transforms(&program)
    });

    println!(
        "[{}] documents: {} reader, {} normalization, {} operation",
        project_config.name,
        programs.reader.document_count(),
        programs.normalization.document_count(),
        programs.operation_text.document_count()
    );

    Ok(())
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::collections::HashMap;

pub fn rtg_1(file: &str) -> usize {
    process(file)
}

pub fn rtg_2(file: &str) -> usize {
    process(file)
}

fn process(file: &str) -> usize {
    let input = File::open(file).expect("File open fail.");
    let reader = BufReader::new(input);

    let lines: Vec<String> = reader.lines()
        .filter_map(Result::ok)
        .collect();

    let mut state = HashMap::new();
    for tokens in lines.iter().map(|line| {
        let mut words = line.split_whitespace().rev();
        let mut tokens = Vec::new();
        loop {
            if let Some(w) = words.next() {
                let w = w.trim_matches(|c: char| !c.is_alphabetic());
                match w {
                    "floor" => {
                        if let Some(f) = words.next() {
                            match f {
                                "first" => tokens.push(Entity::Floor(1)),
                                "second" => tokens.push(Entity::Floor(2)),
                                "third" => tokens.push(Entity::Floor(3)),
                                "fourth" => tokens.push(Entity::Floor(4)),
                                _ => {},
                            }
                        }
                    },
                    "microchip" => {
                        if let Some(m) = words.next() {
                            if let Some(i) = m.find('-') {
                                let (compatibility, _) = m.split_at(i);
                                tokens.push(Entity::Microchip(compatibility))
                            }
                        }
                    },
                    "generator" => {
                        if let Some(g) = words.next() {
                            tokens.push(Entity::Generator(g))
                        }
                    },
                    _ => {},
                }
            } else {
                break;
            }
        }
        tokens
    })
    {
        let mut floor_id = None;
        let mut floor_inventory = Vec::new();
        for token in tokens {
            match token {
                Entity::Floor(n) => floor_id = Some(n),
                e @ _ => floor_inventory.push(e),
            }
        }
        if let Some(f) = floor_id {
            state.insert(f, floor_inventory);
        }
    }

    if let Some(top_floor) = state.iter().map(|s| {
        let (f, _): (&usize, &Vec<Entity>) = s;
        *f
    }).max() {
        let mut elevator = Elevator {
            holding: (None, None),
            current_floor: 1,
            top_floor: top_floor,
        };
        println!("{:?}", elevator);

        loop {
            elevator.inc_floor();
            for entity in state.entry(elevator.current_floor).or_insert(Vec::new()) {
                println!("{:?}", entity);
            }
            println!("{:?}", state);
            if state.entry(4).or_insert(Vec::new()).len() == 4 {
                break;
            }
        }
    }
    42
}

#[derive(Debug, Clone)]
enum Entity<'e> {
    Floor(usize),
    Generator(&'e str),
    Microchip(&'e str),
}

#[derive(Debug)]
struct Elevator<'e> {
    holding: (Option<Entity<'e>>, Option<Entity<'e>>),
    current_floor: usize,
    top_floor: usize,
}

impl<'e> Elevator<'e> {
    fn inc_floor(&mut self) {
        if self.current_floor < self.top_floor {
            self.current_floor += 1;
        }
    }

    fn dec_floor(&mut self) {
        if self.current_floor > 0 {
            self.current_floor -= 1;
        }
    }

    fn hold_item(&mut self, e: &'e Entity) {
        let (ref mut left, ref mut right) = self.holding;
        if !left.is_some() {
            *left = Some(e).cloned();
        } else if !right.is_some() {
            *right = Some(e).cloned();
        }
    }
}
<commit_msg>Update elevator logic. Needs more work.<commit_after>use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::collections::{VecDeque, HashMap};

pub fn rtg_1(file: &str) -> usize {
    process(file)
}

pub fn rtg_2(file: &str) -> usize {
    process(file)
}

fn process(file: &str) -> usize {
    let input = File::open(file).expect("File open fail.");
    let reader = BufReader::new(input);

    let lines: Vec<String> = reader.lines()
        .filter_map(Result::ok)
        .collect();

    let mut state = HashMap::new();
    let mut non_floor_entity_count = 0;
    for tokens in lines.iter().map(|line| {
        let mut words = line.split_whitespace().rev();
        let mut tokens = Vec::new();
        loop {
            if let Some(w) = words.next() {
                let w = w.trim_matches(|c: char| !c.is_alphabetic());
                match w {
                    "floor" => {
                        if let Some(f) = words.next() {
                            match f {
                                "first" => tokens.push(Entity::Floor(1)),
                                "second" => tokens.push(Entity::Floor(2)),
                                "third" => tokens.push(Entity::Floor(3)),
                                "fourth" => tokens.push(Entity::Floor(4)),
                                _ => {},
                            }
                        }
                    },
                    "microchip" => {
                        if let Some(m) = words.next() {
                            if let Some(i) = m.find('-') {
                                let (compatibility, _) = m.split_at(i);
                                tokens.push(Entity::Microchip(compatibility));
                                non_floor_entity_count += 1;
                            }
                        }
                    },
                    "generator" => {
                        if let Some(g) = words.next() {
                            tokens.push(Entity::Generator(g));
                            non_floor_entity_count += 1;
                        }
                    },
                    _ => {},
                }
            } else {
                break;
            }
        }
        tokens
    })
    {
        let mut floor_id = None;
        let mut floor_inventory = VecDeque::new();
        for token in tokens {
            match token {
                Entity::Floor(n) => floor_id = Some(n),
                e @ _ => floor_inventory.push_back(e),
            }
        }
        if let Some(f) = floor_id {
            state.insert(f, floor_inventory);
        }
    }

    if let Some(top_floor) = state.iter().map(|s| {
        let (f, _): (&usize, &VecDeque<Entity>) = s;
        *f
    }).max() {
        let mut elevator = Elevator {
            holding: (None, None),
            current_floor: 1,
            top_floor: top_floor,
        };
        println!("{:?}", elevator);

        loop {
            {
                let inventory = state.entry(elevator.current_floor).or_insert(VecDeque::new());
                println!("Floor {}: {:?}", elevator.current_floor, inventory);
                if let Some(item_a) = elevator.unload_left() {
                    inventory.push_back(item_a);
                }
                if let Some(item_b) = elevator.unload_right() {
                    inventory.push_back(item_b);
                }
                loop {
                    if let Some(item) = inventory.pop_front() {
                        elevator.load_item(item);
                        if elevator.is_full() {
                            break;
                        }
                    } else {
                        panic!("the elevator will only function if it contains at least one RTG or microchip");
                    }
                }

                elevator.inc_floor();
            }
            println!("{:?}", state);
            if state.entry(elevator.top_floor).or_insert(VecDeque::new()).len() == non_floor_entity_count {
                break;
            }
        }
    }
    42
}

#[derive(Debug, Clone)]
enum Entity<'e> {
    Floor(usize),
    Generator(&'e str),
    Microchip(&'e str),
}

#[derive(Debug)]
struct Elevator<'e> {
    holding: (Option<Entity<'e>>, Option<Entity<'e>>),
    current_floor: usize,
    top_floor: usize,
}

impl<'e> Elevator<'e> {
    fn inc_floor(&mut self) {
        if self.current_floor < self.top_floor {
            self.current_floor += 1;
        }
    }

    fn dec_floor(&mut self) {
        if self.current_floor > 0 {
            self.current_floor -= 1;
        }
    }

    fn load_item(&mut self, e: Entity<'e>) {
        if !self.holding.0.is_some() {
            self.holding.0 = Some(e);
        } else if !self.holding.1.is_some() {
            self.holding.1 = Some(e);
        }
    }

    fn is_full(&self) -> bool {
        self.holding.0.is_some() && self.holding.1.is_some()
    }

    fn unload_left(&mut self) -> Option<Entity<'e>> {
        let out = self.holding.0.clone();
        self.holding.0 = None;
        out
    }

    fn unload_right(&mut self) -> Option<Entity<'e>> {
        let out = self.holding.1.clone();
        self.holding.1 = None;
        out
    }
}

// Interesting syntax usage here but I used a method instead
//fn unload_left<'e>(&mut Elevator { holding: (ref mut left, _), .. }: &'e mut Elevator) -> Option<Entity<'e>> {
//    let out = left.clone();
//    *left = None;
//    out
//}
<|endoftext|>


<commit_before>pub mod search;

use std::thread;
use std::cmp::min;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
use std::time::SystemTime;
use std::num::Wrapping;
use basetypes::*;
use uci::{UciEngine, UciEngineFactory, EngineReply, OptionName, OptionDescription};
use position::Position;
use chess_move::*;
use tt::*;

// use rand;
// use rand::distributions::{Sample, Range};
// let mut rng = rand::thread_rng();
// let mut between = Range::new(0, 10);
// let x = between.sample(&mut rng);

const VERSION: &'static str = "0.1";
const MAX_DEPTH: u8 = 126;

/// Implements `UciEngine` trait.
pub struct Engine {
    position: Position,
    pondering_is_allowed: bool,
    is_thinking: bool,
    is_pondering: bool,
    curr_depth: u8,
    replies: Vec<EngineReply>,
    tt: Arc<TranspositionTable>,
    commands: Sender<search::Command>,
    reports: Receiver<search::Report>,
    search_id: usize,
    thinking_since: SystemTime,
    searched_nodes: NodeCount,
    searched_time: u64, // milliseconds
    stop_when: TimeManagement,
}

impl Engine {
    /// Creates a new instance.
    ///
    /// `tt_size_mb` is the preferred size of the transposition
    /// table in Mbytes.
    pub fn new(tt_size_mb: usize) -> Engine {
        let mut tt = TranspositionTable::new();
        tt.resize(tt_size_mb);
        let tt = Arc::new(tt);
        let (commands_tx, commands_rx) = channel();
        let (reports_tx, reports_rx) = channel();
        let tt2 = tt.clone();
        thread::spawn(move || {
            search::run_deepening(tt2, commands_rx, reports_tx);
        });

        Engine {
            position: Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 \
                                          1")
                          .ok()
                          .unwrap(),
            pondering_is_allowed: false,
            is_thinking: false,
            is_pondering: false,
            curr_depth: 0,
            replies: vec![],
            tt: tt,
            commands: commands_tx,
            reports: reports_rx,
            search_id: 0,
            thinking_since: SystemTime::now(),
            searched_nodes: 0,
            searched_time: 0,
            stop_when: TimeManagement::Infinite,
        }
    }
}

impl UciEngine for Engine {
    fn set_option(&mut self, name: &str, value: &str) {
        match name {
            // Tells the engine that it will be allowed to ponder.
            // This option is needed because the engine might change
            // its time management algorithm when pondering is
            // allowed.
            "Ponder" => {
                self.pondering_is_allowed = value == "true";
            }

            // An invalid option. Notice that we do not support
            // re-sizing of the transposition table once the engine
            // had started.
            _ => (),
        }
    }

    fn new_game(&mut self) {
        // Clearing the transposition table would not change anything,
        // so we do nothing.
    }

    fn position(&mut self, fen: &str, moves: &mut Iterator<Item = &str>) {
        if !self.is_thinking {
            if let Ok(p) = Position::from_history(fen, moves) {
                self.position = p;
                self.tt.new_search();
            }
        }
    }

    #[allow(unused_variables)]
    fn go(&mut self,
          searchmoves: Option<Vec<String>>,
          ponder: bool,
          wtime: Option<u64>,
          btime: Option<u64>,
          winc: Option<u64>,
          binc: Option<u64>,
          movestogo: Option<u64>,
          depth: Option<u64>,
          nodes: Option<u64>,
          mate: Option<u64>,
          movetime: Option<u64>,
          infinite: bool) {
        if !self.is_thinking {
            // Note: We ignore the "searchmoves" parameter.

            self.is_thinking = true;
            self.is_pondering = ponder;
            self.search_id = (Wrapping(self.search_id) + Wrapping(1)).0;
            self.thinking_since = SystemTime::now();
            self.searched_nodes = 0;
            self.searched_time = 0;
            self.curr_depth = 0;

            // Figure out when we should stop thinking.
            let (time, inc) = if self.position.board().to_move() == WHITE {
                (wtime, winc.unwrap_or(0))
            } else {
                (btime, binc.unwrap_or(0))
            };
            self.stop_when = if infinite {
                TimeManagement::Infinite
            } else if movetime.is_some() {
                TimeManagement::MoveTime(movetime.unwrap())
            } else if nodes.is_some() {
                TimeManagement::Nodes(nodes.unwrap())
            } else if depth.is_some() {
                TimeManagement::Depth(min(depth.unwrap(), MAX_DEPTH as u64) as u8)
            } else {
                // TODO: Robert Hyatt proposes this:
                //
                // nMoves =  min( numberOfMovesOutOfBook, 10 )
                // factor = 2 - nMoves / 10
                // target = timeLeft / numberOfMovesUntilNextTimeControl
                // time   = factor * target
                let time = time.unwrap_or(0);
                let movestogo = movestogo.unwrap_or(40);
                let movetime = (time + inc * movestogo) / movestogo;
                TimeManagement::MoveTimeHint(min(movetime, time / 2))
            };

            // Start a search with the maximum possible depth.
            self.commands
                .send(search::Command::Search {
                    search_id: self.search_id,
                    position: self.position.clone(),
                    depth: MAX_DEPTH,
                    lower_bound: -20000,
                    upper_bound: 20000,
                })
                .unwrap();
        }
    }

    fn ponder_hit(&mut self) {
        if self.is_thinking && self.is_pondering {
            self.is_pondering = false;
        }
    }

    fn stop(&mut self) {
        if self.is_thinking {
            self.commands.send(search::Command::Stop).unwrap();
            let best_move = self.get_best_move();
            self.replies.push(EngineReply::BestMove {
                best_move: best_move,
                ponder_move: None,
            });
            self.is_thinking = false;
        }
    }

    fn is_thinking(&self) -> bool {
        self.is_thinking
    }

    fn get_reply(&mut self) -> Option<EngineReply> {
        // TODO: implement panic mode for MoveTimeHint management.
        if self.is_thinking && !self.is_pondering {
            if match self.stop_when {
                TimeManagement::MoveTime(t) => self.searched_time > t,
                TimeManagement::MoveTimeHint(t) => self.searched_time > t,
                TimeManagement::Nodes(n) => self.searched_nodes > n,
                TimeManagement::Depth(d) => self.curr_depth > d,
                TimeManagement::Infinite => false,
            } {
                self.stop();
            }
        }

        // Empty the reports queue.
        while let Ok(report) = self.reports.try_recv() {
            if self.is_thinking {
                match report {
                    search::Report::Progress { search_id, searched_nodes, depth }
                        if search_id == self.search_id => {

                        let thinking_duration = self.thinking_since.elapsed().unwrap();
                        self.searched_nodes = searched_nodes;
                        self.searched_time = 1000 * thinking_duration.as_secs() +
                                             (thinking_duration.subsec_nanos() / 1000000) as u64;
                        if self.curr_depth < depth {
                            // We are done with the old depth, so we
                            // report the current primary variation.
                            self.report_pv();
                            self.curr_depth = depth;
                        }
                    }
                    search::Report::Done { search_id, .. } if search_id == self.search_id => {
                        if !self.is_pondering {
                            if let TimeManagement::Infinite = self.stop_when {
                                ()
                            } else {
                                self.stop()
                            }
                        }
                    }
                    _ => (),
                }
            }
        }

        if self.replies.len() > 0 {
            Some(self.replies.remove(0))
        } else {
            None
        }
    }
}

impl Engine {
    // A helper method. It extracts the primary variation (PV) from
    // the transposition table (TT) and sends it to the GUI.
    fn report_pv(&mut self) {
        let mut prev_move = None;
        let mut p = self.position.clone();

        // Extract the PV, the value, and the bound from the TT.
        let mut pv = Vec::new();
        let mut value = -20000;
        let mut bound = BOUND_LOWER;
        while let Some(entry) = self.tt.probe(p.hash()) {
            if pv.len() < self.curr_depth as usize && entry.bound() != BOUND_NONE {
                if let Some(m) = prev_move {
                    pv.push(m);
                }
                if pv.len() & 1 == 0 {
                    value = entry.value();
                    bound = entry.bound();
                } else {
                    // In this case the value stored in `entry` is
                    // from other side's perspective.
                    value = -entry.value();
                    bound = match entry.bound() {
                        BOUND_UPPER => BOUND_LOWER,
                        BOUND_LOWER => BOUND_UPPER,
                        x => x,
                    };
                }
                if bound == BOUND_EXACT {
                    if let Some(m) = p.try_move_digest(entry.move16()) {
                        if p.do_move(m) && !p.is_repeated() {
                            // Try to extend the PV.
                            prev_move = Some(m);
                            continue;
                        }
                    }
                }
            }
            break;
        }

        // Send the extracted info to the GUI.
        let value_suffix = match bound {
            BOUND_EXACT => "",
            BOUND_UPPER => " upperbound",
            BOUND_LOWER => " lowerbound",
            _ => panic!("unexpected bound type"),
        };
        let nps = if self.searched_time == 0 {
            0
        } else {
            1000 * self.searched_nodes / self.searched_time
        };
        let mut pv_string = String::new();
        for m in pv {
            pv_string.push(' ');
            pv_string.push_str(&m.notation());
        }
        self.replies.push(EngineReply::Info(vec![
            ("depth".to_string(), format!("{}", self.curr_depth)),
            ("score".to_string(), format!("cp {}{}", value, value_suffix)),
            ("time".to_string(), format!("{}", self.searched_time)),
            ("nodes".to_string(), format!("{}", self.searched_nodes)),
            ("nps".to_string(), format!("{}", nps)),
            ("pv".to_string(), pv_string),
        ]));
    }

    fn get_best_move(&mut self) -> String {
        let mut m = match self.tt.probe(self.position.hash()) {
            Some(entry) => entry.move16(),
            None => 0,
        };
        if m == 0 {
            // Pick the first legal move.
            let mut first_legal_move = Move::invalid();
            let mut v = MoveStack::new();
            self.position.generate_moves(&mut v);
            while let Some(x) = v.pop() {
                if self.position.do_move(x) {
                    self.position.undo_move();
                    first_legal_move = x;
                    break;
                }
            }
            m = first_legal_move.digest();
        }
        if m != 0 {
            let move_type = move_type(m);
            let orig_square = orig_square(m);
            let dest_square = dest_square(m);
            let promoted_piece = match aux_data(m) {
                0 => "q",
                1 => "r",
                2 => "b",
                3 => "n",
                _ => panic!("invalid promoted piece code"),
            };
            format!("{}{}{}",
                    notation(orig_square),
                    notation(dest_square),
                    if move_type == MOVE_PROMOTION {
                        promoted_piece
                    } else {
                        ""
                    })

        } else {
            "0000".to_string()
        }
    }
}

/// Implements `UciEngineFactory` trait.
pub struct EngineFactory;

impl UciEngineFactory<Engine> for EngineFactory {
    fn name(&self) -> String {
        format!("Alcibiades {}", VERSION)
    }

    fn author(&self) -> String {
        "Evgeni Pandurski".to_string()
    }

    fn options(&self) -> Vec<(OptionName, OptionDescription)> {
        vec![
            // TODO: Calculate a sane limit for the hash size.
            ("Hash".to_string(), OptionDescription::Spin { min: 1, max: 2048, default: 16 }),
            ("Ponder".to_string(), OptionDescription::Check { default: false }),
        ]
    }

    fn create(&self, hash_size_mb: Option<usize>) -> Engine {
        Engine::new(hash_size_mb.unwrap_or(16))
    }
}

enum TimeManagement {
    MoveTime(u64),
    MoveTimeHint(u64),
    Nodes(NodeCount),
    Depth(u8),
    Infinite,
}
<commit_msg>small fix<commit_after>pub mod search;

use std::thread;
use std::cmp::min;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
use std::time::SystemTime;
use std::num::Wrapping;
use basetypes::*;
use uci::{UciEngine, UciEngineFactory, EngineReply, OptionName, OptionDescription};
use position::Position;
use chess_move::*;
use tt::*;

// use rand;
// use rand::distributions::{Sample, Range};
// let mut rng = rand::thread_rng();
// let mut between = Range::new(0, 10);
// let x = between.sample(&mut rng);

const VERSION: &'static str = "0.1";
const MAX_DEPTH: u8 = 126;

/// Implements `UciEngine` trait.
pub struct Engine {
    position: Position,
    pondering_is_allowed: bool,
    is_thinking: bool,
    is_pondering: bool,
    curr_depth: u8,
    replies: Vec<EngineReply>,
    tt: Arc<TranspositionTable>,
    commands: Sender<search::Command>,
    reports: Receiver<search::Report>,
    search_id: usize,
    thinking_since: SystemTime,
    searched_nodes: NodeCount,
    searched_time: u64, // milliseconds
    stop_when: TimeManagement,
}

impl Engine {
    /// Creates a new instance.
    ///
    /// `tt_size_mb` is the preferred size of the transposition
    /// table in Mbytes.
    pub fn new(tt_size_mb: usize) -> Engine {
        let mut tt = TranspositionTable::new();
        tt.resize(tt_size_mb);
        let tt = Arc::new(tt);
        let (commands_tx, commands_rx) = channel();
        let (reports_tx, reports_rx) = channel();
        let tt2 = tt.clone();
        thread::spawn(move || {
            search::run_deepening(tt2, commands_rx, reports_tx);
        });

        Engine {
            position: Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 \
                                          1")
                          .ok()
                          .unwrap(),
            pondering_is_allowed: false,
            is_thinking: false,
            is_pondering: false,
            curr_depth: 0,
            replies: vec![],
            tt: tt,
            commands: commands_tx,
            reports: reports_rx,
            search_id: 0,
            thinking_since: SystemTime::now(),
            searched_nodes: 0,
            searched_time: 0,
            stop_when: TimeManagement::Infinite,
        }
    }
}

impl UciEngine for Engine {
    fn set_option(&mut self, name: &str, value: &str) {
        match name {
            // Tells the engine that it will be allowed to ponder.
            // This option is needed because the engine might change
            // its time management algorithm when pondering is
            // allowed.
            "Ponder" => {
                self.pondering_is_allowed = value == "true";
            }

            // An invalid option. Notice that we do not support
            // re-sizing of the transposition table once the engine
            // had started.
            _ => (),
        }
    }

    fn new_game(&mut self) {
        // Clearing the transposition table would not change anything,
        // so we do nothing.
    }

    fn position(&mut self, fen: &str, moves: &mut Iterator<Item = &str>) {
        if !self.is_thinking {
            if let Ok(p) = Position::from_history(fen, moves) {
                self.position = p;
                self.tt.new_search();
            }
        }
    }

    #[allow(unused_variables)]
    fn go(&mut self,
          searchmoves: Option<Vec<String>>,
          ponder: bool,
          wtime: Option<u64>,
          btime: Option<u64>,
          winc: Option<u64>,
          binc: Option<u64>,
          movestogo: Option<u64>,
          depth: Option<u64>,
          nodes: Option<u64>,
          mate: Option<u64>,
          movetime: Option<u64>,
          infinite: bool) {
        if !self.is_thinking {
            // Note: We ignore the "searchmoves" parameter.

            self.is_thinking = true;
            self.is_pondering = ponder;
            self.search_id = (Wrapping(self.search_id) + Wrapping(1)).0;
            self.thinking_since = SystemTime::now();
            self.searched_nodes = 0;
            self.searched_time = 0;
            self.curr_depth = 0;

            // Figure out when we should stop thinking.
            let (time, inc) = if self.position.board().to_move() == WHITE {
                (wtime, winc.unwrap_or(0))
            } else {
                (btime, binc.unwrap_or(0))
            };
            self.stop_when = if infinite {
                TimeManagement::Infinite
            } else if movetime.is_some() {
                TimeManagement::MoveTime(movetime.unwrap())
            } else if nodes.is_some() {
                TimeManagement::Nodes(nodes.unwrap())
            } else if depth.is_some() {
                TimeManagement::Depth(min(depth.unwrap(), MAX_DEPTH as u64) as u8)
            } else {
                // TODO: Robert Hyatt proposes this:
                //
                // nMoves =  min( numberOfMovesOutOfBook, 10 )
                // factor = 2 - nMoves / 10
                // target = timeLeft / numberOfMovesUntilNextTimeControl
                // time   = factor * target
                let time = time.unwrap_or(0);
                let movestogo = movestogo.unwrap_or(40);
                let movetime = (time + inc * movestogo) / movestogo;
                TimeManagement::MoveTimeHint(min(movetime, time / 2))
            };

            // Start a search with the maximum possible depth.
            self.commands
                .send(search::Command::Search {
                    search_id: self.search_id,
                    position: self.position.clone(),
                    depth: MAX_DEPTH,
                    lower_bound: -20000,
                    upper_bound: 20000,
                })
                .unwrap();
        }
    }

    fn ponder_hit(&mut self) {
        if self.is_thinking && self.is_pondering {
            self.is_pondering = false;
        }
    }

    fn stop(&mut self) {
        if self.is_thinking {
            self.commands.send(search::Command::Stop).unwrap();
            let best_move = self.get_best_move();
            self.replies.push(EngineReply::BestMove {
                best_move: best_move,
                ponder_move: None,
            });
            self.is_thinking = false;
        }
    }

    fn is_thinking(&self) -> bool {
        self.is_thinking
    }

    fn get_reply(&mut self) -> Option<EngineReply> {
        // TODO: implement panic mode for MoveTimeHint management.
        if self.is_thinking && !self.is_pondering {
            if match self.stop_when {
                TimeManagement::MoveTime(t) => self.searched_time >= t,
                TimeManagement::MoveTimeHint(t) => self.searched_time >= t,
                TimeManagement::Nodes(n) => self.searched_nodes >= n,
                TimeManagement::Depth(d) => self.curr_depth > d,
                TimeManagement::Infinite => false,
            } {
                self.stop();
            }
        }

        // Empty the reports queue.
        while let Ok(report) = self.reports.try_recv() {
            if self.is_thinking {
                match report {
                    search::Report::Progress { search_id, searched_nodes, depth }
                        if search_id == self.search_id => {

                        let thinking_duration = self.thinking_since.elapsed().unwrap();
                        self.searched_nodes = searched_nodes;
                        self.searched_time = 1000 * thinking_duration.as_secs() +
                                             (thinking_duration.subsec_nanos() / 1000000) as u64;
                        if self.curr_depth < depth {
                            // We are done with the old depth, so we
                            // report the current primary variation.
                            self.report_pv();
                            self.curr_depth = depth;
                        }
                    }
                    search::Report::Done { search_id, .. } if search_id == self.search_id => {
                        if !self.is_pondering {
                            if let TimeManagement::Infinite = self.stop_when {
                                ()
                            } else {
                                self.stop()
                            }
                        }
                    }
                    _ => (),
                }
            }
        }

        if self.replies.len() > 0 {
            Some(self.replies.remove(0))
        } else {
            None
        }
    }
}

impl Engine {
    // A helper method. It extracts the primary variation (PV) from
    // the transposition table (TT) and sends it to the GUI.
    fn report_pv(&mut self) {
        let mut prev_move = None;
        let mut p = self.position.clone();

        // Extract the PV, the value, and the bound from the TT.
        let mut pv = Vec::new();
        let mut value = -20000;
        let mut bound = BOUND_LOWER;
        while let Some(entry) = self.tt.probe(p.hash()) {
            if pv.len() < self.curr_depth as usize && entry.bound() != BOUND_NONE {
                if let Some(m) = prev_move {
                    pv.push(m);
                }
                if pv.len() & 1 == 0 {
                    value = entry.value();
                    bound = entry.bound();
                } else {
                    // In this case the value stored in `entry` is
                    // from other side's perspective.
                    value = -entry.value();
                    bound = match entry.bound() {
                        BOUND_UPPER => BOUND_LOWER,
                        BOUND_LOWER => BOUND_UPPER,
                        x => x,
                    };
                }
                if bound == BOUND_EXACT {
                    if let Some(m) = p.try_move_digest(entry.move16()) {
                        if p.do_move(m) && !p.is_repeated() {
                            // Try to extend the PV.
                            prev_move = Some(m);
                            continue;
                        }
                    }
                }
            }
            break;
        }

        // Send the extracted info to the GUI.
        let value_suffix = match bound {
            BOUND_EXACT => "",
            BOUND_UPPER => " upperbound",
            BOUND_LOWER => " lowerbound",
            _ => panic!("unexpected bound type"),
        };
        let nps = if self.searched_time == 0 {
            0
        } else {
            1000 * self.searched_nodes / self.searched_time
        };
        let mut pv_string = String::new();
        for m in pv {
            pv_string.push(' ');
            pv_string.push_str(&m.notation());
        }
        self.replies.push(EngineReply::Info(vec![
            ("depth".to_string(), format!("{}", self.curr_depth)),
            ("score".to_string(), format!("cp {}{}", value, value_suffix)),
            ("time".to_string(), format!("{}", self.searched_time)),
            ("nodes".to_string(), format!("{}", self.searched_nodes)),
            ("nps".to_string(), format!("{}", nps)),
            ("pv".to_string(), pv_string),
        ]));
    }

    fn get_best_move(&mut self) -> String {
        let mut m = match self.tt.probe(self.position.hash()) {
            Some(entry) => entry.move16(),
            None => 0,
        };
        if m == 0 {
            // Pick the first legal move.
            let mut first_legal_move = Move::invalid();
            let mut v = MoveStack::new();
            self.position.generate_moves(&mut v);
            while let Some(x) = v.pop() {
                if self.position.do_move(x) {
                    self.position.undo_move();
                    first_legal_move = x;
                    break;
                }
            }
            m = first_legal_move.digest();
        }
        if m != 0 {
            let move_type = move_type(m);
            let orig_square = orig_square(m);
            let dest_square = dest_square(m);
            let promoted_piece = match aux_data(m) {
                0 => "q",
                1 => "r",
                2 => "b",
                3 => "n",
                _ => panic!("invalid promoted piece code"),
            };
            format!("{}{}{}",
                    notation(orig_square),
                    notation(dest_square),
                    if move_type == MOVE_PROMOTION {
                        promoted_piece
                    } else {
                        ""
                    })

        } else {
            "0000".to_string()
        }
    }
}

/// Implements `UciEngineFactory` trait.
pub struct EngineFactory;

impl UciEngineFactory<Engine> for EngineFactory {
    fn name(&self) -> String {
        format!("Alcibiades {}", VERSION)
    }

    fn author(&self) -> String {
        "Evgeni Pandurski".to_string()
    }

    fn options(&self) -> Vec<(OptionName, OptionDescription)> {
        vec![
            // TODO: Calculate a sane limit for the hash size.
            ("Hash".to_string(), OptionDescription::Spin { min: 1, max: 2048, default: 16 }),
            ("Ponder".to_string(), OptionDescription::Check { default: false }),
        ]
    }

    fn create(&self, hash_size_mb: Option<usize>) -> Engine {
        Engine::new(hash_size_mb.unwrap_or(16))
    }
}

enum TimeManagement {
    MoveTime(u64),
    MoveTimeHint(u64),
    Nodes(NodeCount),
    Depth(u8),
    Infinite,
}
<|endoftext|>


<commit_before>// Copyright (C) 2020 Jason Ish
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use crate::eve::eve::EveJson;
use crate::logger::log;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Seek;
use std::io::SeekFrom;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;

#[derive(thiserror::Error, Debug)]
pub enum EveReaderError {
    #[error("failed to parse event")]
    ParseError(String),
    #[error("io error: {0}")]
    IoError(std::io::Error),
}

impl From<std::io::Error> for EveReaderError {
    fn from(err: std::io::Error) -> Self {
        Self::IoError(err)
    }
}

pub struct EveReader {
    pub filename: String,
    line: String,
    reader: Option<BufReader<std::fs::File>>,
    lineno: u64,
    offset: u64,
}

impl EveReader {
    pub fn new(filename: &str) -> Self {
        Self {
            filename: filename.to_string(),
            line: String::new(),
            reader: None,
            lineno: 0,
            offset: 0,
        }
    }

    pub fn open(&mut self) -> Result<(), EveReaderError> {
        let file = File::open(&self.filename)?;
        let reader = BufReader::new(file);
        self.reader = Some(reader);
        self.lineno = 0;
        self.offset = 0;
        Ok(())
    }

    pub fn reopen(&mut self) -> Result<(), EveReaderError> {
        if let Err(err) = self.open() {
            self.reader = None;
            self.lineno = 0;
            self.offset = 0;
            return Err(err);
        }
        Ok(())
    }

    pub fn is_open(&self) -> bool {
        self.reader.is_some()
    }

    pub fn goto_lineno(&mut self, lineno: u64) -> Result<u64, EveReaderError> {
        if self.reader.is_none() {
            self.open()?;
        }
        let mut count = 0;
        for _i in 0..lineno {
            if self.next_line()?.is_none() {
                break;
            }
            count += 1;
        }
        Ok(count)
    }

    pub fn goto_end(&mut self) -> Result<u64, EveReaderError> {
        if self.reader.is_none() {
            self.open()?;
        }
        loop {
            let line = self.next_line()?;
            if line.is_none() {
                break;
            }
        }

        Ok(self.lineno)
    }

    /// Return the current offset the reader is into the file.
    ///
    /// Will return 0 if no file is open.
    pub fn offset(&mut self) -> u64 {
        if let Some(reader) = &mut self.reader {
            if let Ok(pos) = reader.seek(SeekFrom::Current(0)) {
                return pos;
            }
        }
        return 0;
    }

    fn next_line(&mut self) -> Result<Option<&str>, EveReaderError> {
        self.line.truncate(0);
        if let Some(reader) = &mut self.reader {
            let pos = reader.seek(SeekFrom::Current(0))?;
            let n = reader.read_line(&mut self.line)?;
            if n > 0 {
                if !self.line.ends_with('\n') {
                    log::info!(
                        "Line does not end with new line character, seeking back to {}",
                        pos
                    );
                    reader.seek(SeekFrom::Start(pos))?;
                } else {
                    self.offset = pos + n as u64;
                    self.lineno += 1;
                    return Ok(Some(&self.line));
                }
            }
        }
        Ok(None)
    }

    /// Not named next as we don't implement the iterator pattern (yet).
    pub fn next_record(&mut self) -> Result<Option<EveJson>, EveReaderError> {
        if self.reader.is_none() {
            self.open()?;
        }
        if self.reader.is_some() {
            let line = self.next_line()?;
            if line.is_some() {
                let record: EveJson = serde_json::from_str(&self.line).map_err(|err| {
                    log::error!("Failed to parse event: {}", err);
                    EveReaderError::ParseError(self.line.clone())
                })?;
                return Ok(Some(record));
            }
        }
        Ok(None)
    }

    pub fn metadata(&self) -> Option<Metadata> {
        if let Some(reader) = &self.reader {
            match reader.get_ref().metadata() {
                Err(err) => {
                    log::error!("Failed to get metadata for open reader: {}", err);
                    return None;
                }
                Ok(meta) => {
                    let metadata = Metadata {
                        filename: self.filename.clone(),
                        lineno: self.lineno,
                        size: meta.len(),
                        inode: self.inode(&meta),
                    };
                    return Some(metadata);
                }
            }
        }
        None
    }

    // An overly complex method to check if the file on disk has been truncate,
    // or replaced.
    pub fn is_file_changed(&self) -> bool {
        let open: Option<std::fs::Metadata> = if let Some(reader) = &self.reader {
            match reader.get_ref().metadata() {
                Err(err) => {
                    log::debug!("Failed to get metadata for open file: {}", err);
                    None
                }
                Ok(m) => Some(m),
            }
        } else {
            None
        };
        let disk: Option<std::fs::Metadata> = match std::fs::metadata(&self.filename) {
            Err(err) => {
                log::trace!("Failed to get metadata for file on disk: {}", err);
                None
            }
            Ok(m) => Some(m),
        };

        // If neither, then return false.
        if open.is_none() && disk.is_none() {
            log::trace!("open is none, disk is none -> false");
            return false;
        }

        // If we don't have an open file, but there is an on disk file, return true.
        if open.is_none() && disk.is_some() {
            log::trace!("open is none, disk is some -> true");
            return true;
        }

        // If we have a current file, but no on disk file, still return false, it may
        // be in the process of being rotated, or simply deleted with the current file still
        // being written to.
        if open.is_some() && disk.is_none() {
            log::trace!("open is some, disk is none -> false");
            return false;
        }

        // Now we can compare the metadata of the 2 files.
        let open = open.unwrap();
        let disk = disk.unwrap();

        if self.inode(&disk) != self.inode(&open) {
            log::trace!("on disk inode differs from open inode -> true");
            return true;
        }

        // If the file on disk is smaller than the open file, it has been rotated
        // or truncated.
        if disk.len() < self.offset {
            log::trace!("file on disk is smaller than open file -> true");
            return true;
        }

        return false;
    }

    /// Get the size of the file. This is taken directly from disk, so may not be the
    /// exact file currently being read by this reader.
    pub fn file_size(&self) -> u64 {
        if let Ok(metadata) = std::fs::metadata(&self.filename) {
            metadata.len()
        } else {
            0
        }
    }

    #[cfg(unix)]
    fn inode(&self, m: &std::fs::Metadata) -> Option<u64> {
        Some(m.ino())
    }

    #[cfg(not(unix))]
    fn inode(&self, _m: &std::fs::Metadata) -> Option<u64> {
        None
    }
}

#[derive(Debug)]
pub struct Metadata {
    pub filename: String,
    pub lineno: u64,
    pub size: u64,
    pub inode: Option<u64>,
}
<commit_msg>eve-reader: skip empty lines instead of trying to parse<commit_after>// Copyright (C) 2020 Jason Ish
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use crate::eve::eve::EveJson;
use crate::logger::log;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Seek;
use std::io::SeekFrom;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;

#[derive(thiserror::Error, Debug)]
pub enum EveReaderError {
    #[error("failed to parse event")]
    ParseError(String),
    #[error("io error: {0}")]
    IoError(std::io::Error),
}

impl From<std::io::Error> for EveReaderError {
    fn from(err: std::io::Error) -> Self {
        Self::IoError(err)
    }
}

pub struct EveReader {
    pub filename: String,
    line: String,
    reader: Option<BufReader<std::fs::File>>,
    lineno: u64,
    offset: u64,
}

impl EveReader {
    pub fn new(filename: &str) -> Self {
        Self {
            filename: filename.to_string(),
            line: String::new(),
            reader: None,
            lineno: 0,
            offset: 0,
        }
    }

    pub fn open(&mut self) -> Result<(), EveReaderError> {
        let file = File::open(&self.filename)?;
        let reader = BufReader::new(file);
        self.reader = Some(reader);
        self.lineno = 0;
        self.offset = 0;
        Ok(())
    }

    pub fn reopen(&mut self) -> Result<(), EveReaderError> {
        if let Err(err) = self.open() {
            self.reader = None;
            self.lineno = 0;
            self.offset = 0;
            return Err(err);
        }
        Ok(())
    }

    pub fn is_open(&self) -> bool {
        self.reader.is_some()
    }

    pub fn goto_lineno(&mut self, lineno: u64) -> Result<u64, EveReaderError> {
        if self.reader.is_none() {
            self.open()?;
        }
        let mut count = 0;
        for _i in 0..lineno {
            if self.next_line()?.is_none() {
                break;
            }
            count += 1;
        }
        Ok(count)
    }

    pub fn goto_end(&mut self) -> Result<u64, EveReaderError> {
        if self.reader.is_none() {
            self.open()?;
        }
        loop {
            let line = self.next_line()?;
            if line.is_none() {
                break;
            }
        }

        Ok(self.lineno)
    }

    /// Return the current offset the reader is into the file.
    ///
    /// Will return 0 if no file is open.
    pub fn offset(&mut self) -> u64 {
        if let Some(reader) = &mut self.reader {
            if let Ok(pos) = reader.seek(SeekFrom::Current(0)) {
                return pos;
            }
        }
        return 0;
    }

    fn next_line(&mut self) -> Result<Option<&str>, EveReaderError> {
        self.line.truncate(0);
        if let Some(reader) = &mut self.reader {
            let pos = reader.seek(SeekFrom::Current(0))?;
            let n = reader.read_line(&mut self.line)?;
            if n > 0 {
                if !self.line.ends_with('\n') {
                    log::info!(
                        "Line does not end with new line character, seeking back to {}",
                        pos
                    );
                    reader.seek(SeekFrom::Start(pos))?;
                } else {
                    self.offset = pos + n as u64;
                    self.lineno += 1;
                    let line = self.line.trim();
                    return Ok(Some(line));
                }
            }
        }
        return Ok(None);
    }

    /// Not named next as we don't implement the iterator pattern (yet).
    pub fn next_record(&mut self) -> Result<Option<EveJson>, EveReaderError> {
        if self.reader.is_none() {
            self.open()?;
        }
        if self.reader.is_some() {
            loop {
                let line = self.next_line()?;
                if let Some(line) = line {
                    if line.is_empty() {
                        continue;
                    }
                    let record: EveJson = serde_json::from_str(line).map_err(|err| {
                        log::error!("Failed to parse event: {}", err);
                        EveReaderError::ParseError(line.to_string())
                    })?;
                    return Ok(Some(record));
                }
                break;
            }
        }
        Ok(None)
    }

    pub fn metadata(&self) -> Option<Metadata> {
        if let Some(reader) = &self.reader {
            match reader.get_ref().metadata() {
                Err(err) => {
                    log::error!("Failed to get metadata for open reader: {}", err);
                    return None;
                }
                Ok(meta) => {
                    let metadata = Metadata {
                        filename: self.filename.clone(),
                        lineno: self.lineno,
                        size: meta.len(),
                        inode: self.inode(&meta),
                    };
                    return Some(metadata);
                }
            }
        }
        None
    }

    // An overly complex method to check if the file on disk has been truncate,
    // or replaced.
    pub fn is_file_changed(&self) -> bool {
        let open: Option<std::fs::Metadata> = if let Some(reader) = &self.reader {
            match reader.get_ref().metadata() {
                Err(err) => {
                    log::debug!("Failed to get metadata for open file: {}", err);
                    None
                }
                Ok(m) => Some(m),
            }
        } else {
            None
        };
        let disk: Option<std::fs::Metadata> = match std::fs::metadata(&self.filename) {
            Err(err) => {
                log::trace!("Failed to get metadata for file on disk: {}", err);
                None
            }
            Ok(m) => Some(m),
        };

        // If neither, then return false.
        if open.is_none() && disk.is_none() {
            log::trace!("open is none, disk is none -> false");
            return false;
        }

        // If we don't have an open file, but there is an on disk file, return true.
        if open.is_none() && disk.is_some() {
            log::trace!("open is none, disk is some -> true");
            return true;
        }

        // If we have a current file, but no on disk file, still return false, it may
        // be in the process of being rotated, or simply deleted with the current file still
        // being written to.
        if open.is_some() && disk.is_none() {
            log::trace!("open is some, disk is none -> false");
            return false;
        }

        // Now we can compare the metadata of the 2 files.
        let open = open.unwrap();
        let disk = disk.unwrap();

        if self.inode(&disk) != self.inode(&open) {
            log::trace!("on disk inode differs from open inode -> true");
            return true;
        }

        // If the file on disk is smaller than the open file, it has been rotated
        // or truncated.
        if disk.len() < self.offset {
            log::trace!("file on disk is smaller than open file -> true");
            return true;
        }

        return false;
    }

    /// Get the size of the file. This is taken directly from disk, so may not be the
    /// exact file currently being read by this reader.
    pub fn file_size(&self) -> u64 {
        if let Ok(metadata) = std::fs::metadata(&self.filename) {
            metadata.len()
        } else {
            0
        }
    }

    #[cfg(unix)]
    fn inode(&self, m: &std::fs::Metadata) -> Option<u64> {
        Some(m.ino())
    }

    #[cfg(not(unix))]
    fn inode(&self, _m: &std::fs::Metadata) -> Option<u64> {
        None
    }
}

#[derive(Debug)]
pub struct Metadata {
    pub filename: String,
    pub lineno: u64,
    pub size: u64,
    pub inode: Option<u64>,
}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};
use std::vec::Vec;
use error::IndentexError;

const INDENTEX_GLOB: &'static str = "*.inden.tex";

pub fn walk_indentex_files<T: AsRef<Path>>(rootdir: T) -> Result<Vec<PathBuf>, IndentexError> {
    use ignore::WalkBuilder;
    use ignore::types::TypesBuilder;

    // Create a type matcher
    let mut tb = TypesBuilder::new();
    tb.add("indentex", INDENTEX_GLOB).unwrap();
    tb.select("indentex");
    let matcher = tb.build().unwrap();

    // Walk the path
    let mut files: Vec<PathBuf> = Vec::new();
    for res in WalkBuilder::new(rootdir.as_ref()).types(matcher).build() {
        let item = res?;
        if item.file_type().unwrap().is_file() {
            files.push(item.path().to_path_buf());
        }
    }

    Ok(files)
}

#[inline]
fn is_indentex_file<T: AsRef<Path>>(filepath: T) -> bool {
    use globset::Glob;

    let glob = Glob::new(INDENTEX_GLOB).unwrap().compile_matcher();
    glob.is_match(filepath.as_ref())
}

#[test]
fn test_is_indentex_file() {
    assert!(!is_indentex_file(Path::new("foo")));
    assert!(!is_indentex_file(Path::new("foo.tex")));
    assert!(!is_indentex_file(Path::new("foo_inden.tex")));

    assert!(is_indentex_file(Path::new("foo.inden.tex")));
    assert!(is_indentex_file(Path::new("foo.bar.inden.tex")));
}

/// Rename an `*.inden.tex` file into `*_inden.tex`
pub fn rename_indentex_file<T: AsRef<Path>>(old_path: T) -> Result<PathBuf, IndentexError> {
    if !is_indentex_file(old_path.as_ref()) {
        return Err(IndentexError::InvalidExtension);
    }

    let mut new_pathbuf = old_path.as_ref().to_path_buf();
    // Strip both extensions, first `.tex` and then `.inden`
    // We assume that the file `old_path` is a valid indentex file,
    // i.e. it has the
    new_pathbuf.set_extension("");
    new_pathbuf.set_extension("");
    // Get the full filename (i.e. with all dots etc. if there are any)
    let mut new_name = new_pathbuf.file_name().unwrap().to_os_string();
    new_name.push("_indentex.tex");
    new_pathbuf.pop();
    new_pathbuf.push(new_name);

    Ok(new_pathbuf)
}

#[test]
fn test_rename_indentex_file() {
    assert_eq!(rename_indentex_file(Path::new("./foo.inden.tex")).unwrap(),
               PathBuf::from("./foo_indentex.tex"));
    assert_eq!(rename_indentex_file(Path::new("./foo.bar.inden.tex")).unwrap(),
               PathBuf::from("./foo.bar_indentex.tex"));
    assert_eq!(rename_indentex_file(Path::new("./.foo.bar.inden.tex")).unwrap(),
               PathBuf::from("./.foo.bar_indentex.tex"));
    assert_eq!(rename_indentex_file(Path::new("foo.inden.tex")).unwrap(),
               PathBuf::from("foo_indentex.tex"));
    assert!(rename_indentex_file(Path::new("foo.bar.tex")).is_err())
}

/// Read a file line by line, right-trim lines and _copy_ them into a vec of strings
pub fn read_and_trim_lines<T: AsRef<Path>>(path: T) -> Result<Vec<String>, IndentexError> {
    use std::fs::File;
    use std::io::{BufReader, BufRead};

    if !is_indentex_file(path.as_ref()) {
        return Err(IndentexError::InvalidExtension);
    }

    let file = File::open(path.as_ref())?;
    let buf = BufReader::new(file);

    let mut lines = Vec::new();
    for l in buf.lines() {
        let trimmed_line = l?.trim_right().to_string();
        lines.push(trimmed_line);
    }

    Ok(lines)
}

pub fn write_to_file<T, U>(path: T, data: U) -> Result<(), IndentexError>
    where T: AsRef<Path>,
          U: AsRef<str>
{

    use std::fs::File;
    use std::io::{BufWriter, Write};

    let file = File::create(path.as_ref())?;
    let mut buf = BufWriter::new(file);
    buf.write(data.as_ref().as_bytes())?;

    Ok(())
}
<commit_msg>Refactored a for loop into map<commit_after>use std::path::{Path, PathBuf};
use std::vec::Vec;
use error::IndentexError;

const INDENTEX_GLOB: &'static str = "*.inden.tex";

pub fn walk_indentex_files<T: AsRef<Path>>(rootdir: T) -> Result<Vec<PathBuf>, IndentexError> {
    use ignore::WalkBuilder;
    use ignore::types::TypesBuilder;

    // Create a type matcher
    let mut tb = TypesBuilder::new();
    tb.add("indentex", INDENTEX_GLOB).unwrap();
    tb.select("indentex");
    let matcher = tb.build().unwrap();

    // Walk the path
    let mut files: Vec<PathBuf> = Vec::new();
    for res in WalkBuilder::new(rootdir.as_ref()).types(matcher).build() {
        let item = res?;
        if item.file_type().unwrap().is_file() {
            files.push(item.path().to_path_buf());
        }
    }

    Ok(files)
}

#[inline]
fn is_indentex_file<T: AsRef<Path>>(filepath: T) -> bool {
    use globset::Glob;

    let glob = Glob::new(INDENTEX_GLOB).unwrap().compile_matcher();
    glob.is_match(filepath.as_ref())
}

#[test]
fn test_is_indentex_file() {
    assert!(!is_indentex_file(Path::new("foo")));
    assert!(!is_indentex_file(Path::new("foo.tex")));
    assert!(!is_indentex_file(Path::new("foo_inden.tex")));

    assert!(is_indentex_file(Path::new("foo.inden.tex")));
    assert!(is_indentex_file(Path::new("foo.bar.inden.tex")));
}

/// Rename an `*.inden.tex` file into `*_inden.tex`
pub fn rename_indentex_file<T: AsRef<Path>>(old_path: T) -> Result<PathBuf, IndentexError> {
    if !is_indentex_file(old_path.as_ref()) {
        return Err(IndentexError::InvalidExtension);
    }

    let mut new_pathbuf = old_path.as_ref().to_path_buf();
    // Strip both extensions, first `.tex` and then `.inden`
    // We assume that the file `old_path` is a valid indentex file,
    // i.e. it has the
    new_pathbuf.set_extension("");
    new_pathbuf.set_extension("");
    // Get the full filename (i.e. with all dots etc. if there are any)
    let mut new_name = new_pathbuf.file_name().unwrap().to_os_string();
    new_name.push("_indentex.tex");
    new_pathbuf.pop();
    new_pathbuf.push(new_name);

    Ok(new_pathbuf)
}

#[test]
fn test_rename_indentex_file() {
    assert_eq!(rename_indentex_file(Path::new("./foo.inden.tex")).unwrap(),
               PathBuf::from("./foo_indentex.tex"));
    assert_eq!(rename_indentex_file(Path::new("./foo.bar.inden.tex")).unwrap(),
               PathBuf::from("./foo.bar_indentex.tex"));
    assert_eq!(rename_indentex_file(Path::new("./.foo.bar.inden.tex")).unwrap(),
               PathBuf::from("./.foo.bar_indentex.tex"));
    assert_eq!(rename_indentex_file(Path::new("foo.inden.tex")).unwrap(),
               PathBuf::from("foo_indentex.tex"));
    assert!(rename_indentex_file(Path::new("foo.bar.tex")).is_err())
}

/// Read a file line by line, right-trim lines and _copy_ them into a vec of strings
pub fn read_and_trim_lines<T: AsRef<Path>>(path: T) -> Result<Vec<String>, IndentexError> {
    use std::fs::File;
    use std::io::{BufReader, BufRead};

    if !is_indentex_file(path.as_ref()) {
        return Err(IndentexError::InvalidExtension);
    }

    let file = File::open(path.as_ref())?;
    let buf = BufReader::new(file);

    buf.lines().map(|r| Ok(r?.trim_right().to_string())).collect()
}

pub fn write_to_file<T, U>(path: T, data: U) -> Result<(), IndentexError>
    where T: AsRef<Path>,
          U: AsRef<str>
{

    use std::fs::File;
    use std::io::{BufWriter, Write};

    let file = File::create(path.as_ref())?;
    let mut buf = BufWriter::new(file);
    buf.write(data.as_ref().as_bytes())?;

    Ok(())
}
<|endoftext|>