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

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

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

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

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

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

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

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

        Ok(PromptResult::NotDone)
    }

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

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

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

        text
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            Ok(Command::Add(args[0].into(), id))
        },
    },
    Delete => {
        name: "delete",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::Delete),
    },
    LoginToken(_) => {
        name: "token",
        usage: "<token>",
        min_args: 1,
        fn: |args: &[&str]| {
            Ok(Command::LoginToken(args.join(" ")))
        },
    },
    PlayerArgs(_) => {
        name: "args",
        usage: "<player args>",
        min_args: 0,
        fn: |args: &[&str]| {
            let args = args.iter()
                .map(|frag| frag.to_string())
                .collect();

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

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

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

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

        const MIN_CONFIDENCE: f32 = 0.7;

        let names = Command::COMMANDS.iter().map(|cmd| Cow::Borrowed(cmd.name));
        let idx = detect::closest_str_match_idx(names, name, MIN_CONFIDENCE, strsim::jaro_winkler)?;

        Some(&Command::COMMANDS[idx])
    }
}

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

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

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

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

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

        let mut prompt = CommandPrompt::new();

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

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

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

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

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

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

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

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

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

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

        test_command!("status watching\n", Command::Status(Status::Watching));
    }
}
<commit_msg>anup: reserve space for 32 characters when creating CommandPrompt<commit_after>use crate::err::{self, Result};
use smallvec::{smallvec, SmallVec};
use snafu::ensure;
use std::convert::TryFrom;
use std::result;
use termion::event::Key;
use tui::style::{Color, Style};
use tui::widgets::Text;
use unicode_width::UnicodeWidthChar;

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

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

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

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

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

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

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

        Ok(PromptResult::NotDone)
    }

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

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

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

        text
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            Ok(Command::Add(args[0].into(), id))
        },
    },
    Delete => {
        name: "delete",
        usage: "",
        min_args: 0,
        fn: |_| Ok(Command::Delete),
    },
    LoginToken(_) => {
        name: "token",
        usage: "<token>",
        min_args: 1,
        fn: |args: &[&str]| {
            Ok(Command::LoginToken(args.join(" ")))
        },
    },
    PlayerArgs(_) => {
        name: "args",
        usage: "<player args>",
        min_args: 0,
        fn: |args: &[&str]| {
            let args = args.iter()
                .map(|frag| frag.to_string())
                .collect();

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

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

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

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

        const MIN_CONFIDENCE: f32 = 0.7;

        let names = Command::COMMANDS.iter().map(|cmd| Cow::Borrowed(cmd.name));
        let idx = detect::closest_str_match_idx(names, name, MIN_CONFIDENCE, strsim::jaro_winkler)?;

        Some(&Command::COMMANDS[idx])
    }
}

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

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

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

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

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

        let mut prompt = CommandPrompt::new();

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

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

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

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

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

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

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

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

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

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

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


<commit_before>// Copyright (C) 2016  ParadoxSpiral
//
// This file is part of mpv-rs.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#![allow(dead_code, improper_ctypes, missing_docs)]
use libc;

pub mod prototype {
    #[repr(C)]
    /// This is ok, because you only ever need a `*mut MpvHandle`.
    pub struct MpvHandle;
}

enum_from_primitive! {
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum MpvError {
        // Success = 0, Handled internally
        EventQueueFull = -1,
        NoMem = -2,
        Uninitialized = -3,
        InvalidParameter = -4,
        OptionNotFound = -5,
        OptionFormat = -6,
        OptionError = -7,
        PropertyNotFound = -8,
        PropertyFormat = -9,
        PropertyUnavailable = -10,
        PropertyError = -11,
        Command = -12,
        LoadingFailed = -13,
        AoInitFailed = -14,
        VoInitFailed = -15,
        NothingToPlay = -16,
        UnknownFormat = -17,
        Unsupported = -18,
        NotImplemented = -19,
        Generic = -20,
    }
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvFormat {
    None = 0,
    String = 1,
    OsdString = 2,
    Flag = 3,
    Int64 = 4,
    Double = 5,
    Node = 6,
    NodeArray = 7,
    NodeMap = 8,
    ByteArray = 9,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Eq)]
pub struct MpvNode {
    // FIXME:
    // union {
    // char *string;
    // int flag;
    // int64_t int64;
    // double double_;
    // struct mpv_node_list *list;
    // struct mpv_byte_array *ba;
    // } u;
    pub format: MpvFormat,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvNodeList {
    pub num: libc::c_int,
    pub values: *mut MpvNode,
    pub keys: *mut *mut libc::c_char,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvByteArray {
    pub data: *mut libc::c_void,
    pub size: libc::size_t,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvEventId {
    None = 0,
    Shutdown = 1,
    LogMessage = 2,
    GetPropertyReply = 3,
    SetPropertyReply = 4,
    CommandReply = 5,
    StartFile = 6,
    EndFile = 7,
    FileLoaded = 8,
    TracksChanged = 9,
    TrackSwitched = 10,
    Idle = 11,
    Pause = 12,
    Unpause = 13,
    Tick = 14,
    ScriptInputDispatch = 15,
    ClientMessage = 16,
    VideoReconfig = 17,
    AudioReconfig = 18,
    MetadataUpdate = 19,
    Seek = 20,
    PlaybackRestart = 21,
    PropertyChange = 22,
    ChapterChange = 23,
    QueueOverflow = 24,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvEventProperty {
    pub name: *const libc::c_char,
    pub format: MpvFormat,
    pub data: *mut libc::c_void,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvLogLevel {
    None = 0,
    Fatal = 10,
    Error = 20,
    Warn = 30,
    Info = 40,
    V = 50,
    Debug = 60,
    Trace = 70,
}

#[repr(C)]
pub struct MpvEventLogMessage {
    pub prefix: *const libc::c_char,
    pub level: *const libc::c_char,
    pub text: *const libc::c_char,
    pub log_level: MpvLogLevel,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvEndFileReason {
    Eof = 0,
    Stop = 2,
    Quit = 3,
    Error = 4,
    Redirect = 5,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MpvEventEndFile {
    pub reason: libc::c_int,
    pub error: libc::c_int,
}

#[repr(C)]
pub struct MpvEventScriptInputDispatch {
    pub arg0: libc::c_int,
    pub type_: *const libc::c_char,
}

#[repr(C)]
pub struct MpvEventClientMessage {
    pub num_args: libc::c_int,
    pub args: *mut *const libc::c_char,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvEvent {
    pub event_id: MpvEventId,
    pub error: libc::c_int,
    pub reply_userdata: libc::uint64_t,
    pub data: *mut libc::c_void,
}

// Needed because used in lazy_static.
unsafe impl Send for MpvEvent {}
unsafe impl Sync for MpvEvent {}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvSubApi {
    OpenglCb = 1,
}

#[cfg_attr(feature="static", link(name = "mpv", kind = "static"))]
#[cfg_attr(not(feature="static"), link(name = "mpv"))]
extern "C" {
    pub fn mpv_client_api_version() -> u32;
    pub fn mpv_error_string(error: libc::c_int) -> *const libc::c_char;
    pub fn mpv_free(data: *mut libc::c_void);
    pub fn mpv_client_name(ctx: *const prototype::MpvHandle) -> *const libc::c_char;
    pub fn mpv_create() -> *mut prototype::MpvHandle;
    pub fn mpv_initialize(ctx: *mut prototype::MpvHandle) -> libc::c_int;
    pub fn mpv_detach_destroy(ctx: *mut prototype::MpvHandle);
    pub fn mpv_terminate_destroy(ctx: *mut prototype::MpvHandle);
    pub fn mpv_create_client(ctx: *mut prototype::MpvHandle,
                             name: *const libc::c_char)
                             -> *mut prototype::MpvHandle;
    pub fn mpv_load_config_file(ctx: *mut prototype::MpvHandle,
                                filename: *const libc::c_char)
                                -> libc::c_int;
    pub fn mpv_suspend(ctx: *mut prototype::MpvHandle);
    pub fn mpv_resume(ctx: *mut prototype::MpvHandle);
    pub fn mpv_free_node_contents(node: *mut MpvNode);
    pub fn mpv_set_option(ctx: *mut prototype::MpvHandle,
                          name: *const libc::c_char,
                          format: libc::c_int,
                          data: *mut libc::c_void)
                          -> libc::c_int;
    pub fn mpv_set_option_string(ctx: *mut prototype::MpvHandle,
                                 name: *const libc::c_char,
                                 data: *const libc::c_char)
                                 -> libc::c_int;
    pub fn mpv_command(ctx: *mut prototype::MpvHandle,
                       args: *mut *const libc::c_char)
                       -> libc::c_int;
    pub fn mpv_command_node(ctx: *mut prototype::MpvHandle,
                            args: *mut MpvNode,
                            result: *mut MpvNode)
                            -> libc::c_int;
    pub fn mpv_command_string(ctx: *mut prototype::MpvHandle,
                              args: *const libc::c_char)
                              -> libc::c_int;
    pub fn mpv_get_time_us(ctx: *mut prototype::MpvHandle) -> libc::int64_t;
    pub fn mpv_command_async(ctx: *mut prototype::MpvHandle,
                             reply_userdata: libc::uint64_t,
                             args: *const *const libc::c_char)
                             -> libc::c_int;
    pub fn mpv_set_property(ctx: *mut prototype::MpvHandle,
                            name: *const libc::c_char,
                            format: libc::c_int,
                            data: *mut libc::c_void)
                            -> libc::c_int;
    pub fn mpv_set_property_string(ctx: *mut prototype::MpvHandle,
                                   name: *const libc::c_char,
                                   data: *const libc::c_char)
                                   -> libc::c_int;
    pub fn mpv_set_property_async(ctx: *mut prototype::MpvHandle,
                                  reply_userdata: libc::uint64_t,
                                  name: *const libc::c_char,
                                  format: libc::c_int,
                                  data: *mut libc::c_void)
                                  -> libc::c_int;
    pub fn mpv_get_property(ctx: *mut prototype::MpvHandle,
                            name: *const libc::c_char,
                            format: libc::c_int,
                            data: *mut libc::c_void)
                            -> libc::c_int;
    pub fn mpv_get_property_string(ctx: *mut prototype::MpvHandle,
                                   name: *const libc::c_char)
                                   -> *mut libc::c_char;
    pub fn mpv_get_property_osd_string(ctx: *mut prototype::MpvHandle,
                                       name: *const libc::c_char)
                                       -> *mut libc::c_char;
    pub fn mpv_get_property_async(ctx: *mut prototype::MpvHandle,
                                  reply_userdata: libc::uint64_t,
                                  name: *const libc::c_char,
                                  format: libc::c_int)
                                  -> libc::c_int;
    pub fn mpv_observe_property(mpv: *mut prototype::MpvHandle,
                                reply_userdata: libc::uint64_t,
                                name: *const libc::c_char,
                                format: libc::c_int)
                                -> libc::c_int;
    pub fn mpv_unobserve_property(mpv: *mut prototype::MpvHandle,
                                  registered_reply_userdata: libc::uint64_t)
                                  -> libc::c_int;
    pub fn mpv_event_name(event: MpvEventId) -> *const libc::c_char;
    pub fn mpv_request_event(ctx: *mut prototype::MpvHandle,
                             event: MpvEventId,
                             enable: libc::c_int)
                             -> libc::c_int;
    pub fn mpv_request_log_messages(ctx: *mut prototype::MpvHandle,
                                    min_level: *const libc::c_char)
                                    -> libc::c_int;
    pub fn mpv_wait_event(ctx: *mut prototype::MpvHandle,
                          timeout: libc::c_double)
                          -> *mut MpvEvent;
    pub fn mpv_wakeup(ctx: *mut prototype::MpvHandle);
    pub fn mpv_set_wakeup_callback(ctx: *mut prototype::MpvHandle,
                                   cb: unsafe extern "C" fn(*mut libc::c_void),
                                   d: *mut libc::c_void);
    pub fn mpv_get_wakeup_pipe(ctx: *mut prototype::MpvHandle) -> libc::c_int;
    pub fn mpv_wait_async_requests(ctx: *mut prototype::MpvHandle);
    pub fn mpv_get_sub_api(ctx: *mut prototype::MpvHandle, sub_api: MpvSubApi);
}
<commit_msg>Add raw bindings for additions in libmpv 1.22, cleanup<commit_after>// Copyright (C) 2016  ParadoxSpiral
//
// This file is part of mpv-rs.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#![allow(dead_code, improper_ctypes, missing_docs, non_camel_case_types)]
use libc;

pub mod prototype {
    #[repr(C)]
    /// This is ok, because you only ever need a `*mut MpvHandle`.
    pub struct MpvHandle;
}

enum_from_primitive! {
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum MpvError {
        // Success = 0, Handled internally
        EventQueueFull = -1,
        NoMem = -2,
        Uninitialized = -3,
        InvalidParameter = -4,
        OptionNotFound = -5,
        OptionFormat = -6,
        OptionError = -7,
        PropertyNotFound = -8,
        PropertyFormat = -9,
        PropertyUnavailable = -10,
        PropertyError = -11,
        Command = -12,
        LoadingFailed = -13,
        AoInitFailed = -14,
        VoInitFailed = -15,
        NothingToPlay = -16,
        UnknownFormat = -17,
        Unsupported = -18,
        NotImplemented = -19,
        Generic = -20,
    }
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvFormat {
    None = 0,
    String = 1,
    OsdString = 2,
    Flag = 3,
    Int64 = 4,
    Double = 5,
    Node = 6,
    NodeArray = 7,
    NodeMap = 8,
    ByteArray = 9,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Eq)]
pub struct MpvNode {
    // FIXME:
    // union {
    // char *string;
    // int flag;
    // int64_t int64;
    // double double_;
    // struct mpv_node_list *list;
    // struct mpv_byte_array *ba;
    // } u;
    pub format: MpvFormat,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvNodeList {
    pub num: libc::c_int,
    pub values: *mut MpvNode,
    pub keys: *mut *mut libc::c_char,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvByteArray {
    pub data: *mut libc::c_void,
    pub size: libc::size_t,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvEventId {
    None = 0,
    Shutdown = 1,
    LogMessage = 2,
    GetPropertyReply = 3,
    SetPropertyReply = 4,
    CommandReply = 5,
    StartFile = 6,
    EndFile = 7,
    FileLoaded = 8,
    TracksChanged = 9,
    TrackSwitched = 10,
    Idle = 11,
    Pause = 12,
    Unpause = 13,
    Tick = 14,
    ScriptInputDispatch = 15,
    ClientMessage = 16,
    VideoReconfig = 17,
    AudioReconfig = 18,
    MetadataUpdate = 19,
    Seek = 20,
    PlaybackRestart = 21,
    PropertyChange = 22,
    ChapterChange = 23,
    QueueOverflow = 24,
}

#[repr(C)]
#[derive(Debug)]
pub struct MpvEventProperty {
    pub name: *const libc::c_char,
    pub format: MpvFormat,
    pub data: *mut libc::c_void,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvLogLevel {
    None = 0,
    Fatal = 10,
    Error = 20,
    Warn = 30,
    Info = 40,
    V = 50,
    Debug = 60,
    Trace = 70,
}

#[repr(C)]
pub struct MpvEventLogMessage {
    pub prefix: *const libc::c_char,
    pub level: *const libc::c_char,
    pub text: *const libc::c_char,
    pub log_level: MpvLogLevel,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvEndFileReason {
    Eof = 0,
    Stop = 2,
    Quit = 3,
    Error = 4,
    Redirect = 5,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MpvEventEndFile {
    pub reason: libc::c_int,
    pub error: libc::c_int,
}

#[repr(C)]
pub struct MpvEventScriptInputDispatch {
    pub arg0: libc::c_int,
    pub type_: *const libc::c_char,
}

#[repr(C)]
pub struct MpvEventClientMessage {
    pub num_args: libc::c_int,
    pub args: *mut *const libc::c_char,
}

#[repr(C)]
pub struct MpvEvent {
    pub event_id: MpvEventId,
    pub error: libc::c_int,
    pub reply_userdata: libc::uint64_t,
    pub data: *mut libc::c_void,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpvSubApi {
    OpenglCb = 1,
}

pub type mpv_stream_cb_read_fn = extern "C" fn(cookie: *mut libc::c_void,
                                               buf: *mut libc::c_char,
                                               nbytes: libc::uint64_t) -> libc::int64_t;
pub type mpv_stream_cb_seek_fn = extern "C" fn(cookie: *mut libc::c_void, offset: libc::int64_t)
                                                                                -> libc::int64_t;
pub type mpv_stream_cb_size_fn = extern "C" fn(cookie: *mut libc::c_void) -> libc::int64_t;
pub type mpv_stream_cb_close_fn = extern "C" fn(cookie: *mut libc::c_void) -> libc::c_void;

pub type mpv_stream_cb_open_ro_fn = extern "C" fn(user_data: *mut libc::c_void,
                                                  uri: *mut libc::c_char,
                                                  info: *mut mpv_stream_cb_info) -> libc::c_int;
pub type mpv_stream_cb_add_ro = extern "C" fn(ctx: *mut prototype::MpvHandle,
                                              protocol: *const libc::c_char,
                                              user_data: *mut libc::c_void,
                                              open_fn: mpv_stream_cb_open_ro_fn) -> libc::c_int;

#[repr(C)]
pub struct mpv_stream_cb_info {
    pub cookie: *mut libc::c_void,

    pub read_fn: mpv_stream_cb_read_fn,
    pub seek_fn: mpv_stream_cb_seek_fn,
    pub size_fn: mpv_stream_cb_size_fn,
    pub close_fn: mpv_stream_cb_close_fn,
}

#[cfg_attr(feature="static", link(name = "mpv", kind = "static"))]
#[cfg_attr(not(feature="static"), link(name = "mpv"))]
extern "C" {
    pub fn mpv_client_api_version() -> u32;
    pub fn mpv_error_string(error: libc::c_int) -> *const libc::c_char;
    pub fn mpv_free(data: *mut libc::c_void);
    pub fn mpv_client_name(ctx: *const prototype::MpvHandle) -> *const libc::c_char;
    pub fn mpv_create() -> *mut prototype::MpvHandle;
    pub fn mpv_initialize(ctx: *mut prototype::MpvHandle) -> libc::c_int;
    pub fn mpv_detach_destroy(ctx: *mut prototype::MpvHandle);
    pub fn mpv_terminate_destroy(ctx: *mut prototype::MpvHandle);
    pub fn mpv_create_client(ctx: *mut prototype::MpvHandle,
                             name: *const libc::c_char)
                             -> *mut prototype::MpvHandle;
    pub fn mpv_load_config_file(ctx: *mut prototype::MpvHandle,
                                filename: *const libc::c_char)
                                -> libc::c_int;
    pub fn mpv_suspend(ctx: *mut prototype::MpvHandle);
    pub fn mpv_resume(ctx: *mut prototype::MpvHandle);
    pub fn mpv_free_node_contents(node: *mut MpvNode);
    pub fn mpv_set_option(ctx: *mut prototype::MpvHandle,
                          name: *const libc::c_char,
                          format: libc::c_int,
                          data: *mut libc::c_void)
                          -> libc::c_int;
    pub fn mpv_set_option_string(ctx: *mut prototype::MpvHandle,
                                 name: *const libc::c_char,
                                 data: *const libc::c_char)
                                 -> libc::c_int;
    pub fn mpv_command(ctx: *mut prototype::MpvHandle,
                       args: *mut *const libc::c_char)
                       -> libc::c_int;
    pub fn mpv_command_node(ctx: *mut prototype::MpvHandle,
                            args: *mut MpvNode,
                            result: *mut MpvNode)
                            -> libc::c_int;
    pub fn mpv_command_string(ctx: *mut prototype::MpvHandle,
                              args: *const libc::c_char)
                              -> libc::c_int;
    pub fn mpv_get_time_us(ctx: *mut prototype::MpvHandle) -> libc::int64_t;
    pub fn mpv_command_async(ctx: *mut prototype::MpvHandle,
                             reply_userdata: libc::uint64_t,
                             args: *const *const libc::c_char)
                             -> libc::c_int;
    pub fn mpv_set_property(ctx: *mut prototype::MpvHandle,
                            name: *const libc::c_char,
                            format: libc::c_int,
                            data: *mut libc::c_void)
                            -> libc::c_int;
    pub fn mpv_set_property_string(ctx: *mut prototype::MpvHandle,
                                   name: *const libc::c_char,
                                   data: *const libc::c_char)
                                   -> libc::c_int;
    pub fn mpv_set_property_async(ctx: *mut prototype::MpvHandle,
                                  reply_userdata: libc::uint64_t,
                                  name: *const libc::c_char,
                                  format: libc::c_int,
                                  data: *mut libc::c_void)
                                  -> libc::c_int;
    pub fn mpv_get_property(ctx: *mut prototype::MpvHandle,
                            name: *const libc::c_char,
                            format: libc::c_int,
                            data: *mut libc::c_void)
                            -> libc::c_int;
    pub fn mpv_get_property_string(ctx: *mut prototype::MpvHandle,
                                   name: *const libc::c_char)
                                   -> *mut libc::c_char;
    pub fn mpv_get_property_osd_string(ctx: *mut prototype::MpvHandle,
                                       name: *const libc::c_char)
                                       -> *mut libc::c_char;
    pub fn mpv_get_property_async(ctx: *mut prototype::MpvHandle,
                                  reply_userdata: libc::uint64_t,
                                  name: *const libc::c_char,
                                  format: libc::c_int)
                                  -> libc::c_int;
    pub fn mpv_observe_property(mpv: *mut prototype::MpvHandle,
                                reply_userdata: libc::uint64_t,
                                name: *const libc::c_char,
                                format: libc::c_int)
                                -> libc::c_int;
    pub fn mpv_unobserve_property(mpv: *mut prototype::MpvHandle,
                                  registered_reply_userdata: libc::uint64_t)
                                  -> libc::c_int;
    pub fn mpv_event_name(event: MpvEventId) -> *const libc::c_char;
    pub fn mpv_request_event(ctx: *mut prototype::MpvHandle,
                             event: MpvEventId,
                             enable: libc::c_int)
                             -> libc::c_int;
    pub fn mpv_request_log_messages(ctx: *mut prototype::MpvHandle,
                                    min_level: *const libc::c_char)
                                    -> libc::c_int;
    pub fn mpv_wait_event(ctx: *mut prototype::MpvHandle,
                          timeout: libc::c_double)
                          -> *mut MpvEvent;
    pub fn mpv_wakeup(ctx: *mut prototype::MpvHandle);
    pub fn mpv_set_wakeup_callback(ctx: *mut prototype::MpvHandle,
                                   cb: unsafe extern "C" fn(*mut libc::c_void),
                                   d: *mut libc::c_void);
    pub fn mpv_get_wakeup_pipe(ctx: *mut prototype::MpvHandle) -> libc::c_int;
    pub fn mpv_wait_async_requests(ctx: *mut prototype::MpvHandle);
    pub fn mpv_get_sub_api(ctx: *mut prototype::MpvHandle, sub_api: MpvSubApi);
}
<|endoftext|>


<commit_before>use crate::{
    heartbeat::Heartbeat,
    socket_state::{SocketEvent, SocketStateHandle},
    tcp::TcpStream,
    thread::ThreadHandle,
    Result,
};
use log::trace;
use mio::{Events, Interest, Poll, Token};
use std::{
    collections::HashMap,
    fmt,
    sync::{
        atomic::{AtomicBool, AtomicUsize, Ordering},
        Arc,
    },
    thread::Builder as ThreadBuilder,
};

pub trait ReactorBuilder: fmt::Debug + Send + Sync {
    fn build(&self, heartbeat: Heartbeat) -> Result<Box<dyn Reactor + Send>>;
}

pub trait Reactor: fmt::Debug + Send {
    fn register(&mut self, socket: &mut TcpStream, socket_state: SocketStateHandle) -> Result<()>;
    fn handle(&self) -> Box<dyn ReactorHandle + Send> {
        Box::new(DummyHandle)
    }
    fn start(self: Box<Self>) -> Result<ThreadHandle> {
        Ok(ThreadHandle::default())
    }
}

pub trait ReactorHandle {
    fn shutdown(&self);
}

pub(crate) struct DefaultReactorBuilder;

impl ReactorBuilder for DefaultReactorBuilder {
    fn build(&self, heartbeat: Heartbeat) -> Result<Box<dyn Reactor + Send>> {
        Ok(Box::new(DefaultReactor::new(heartbeat)?))
    }
}

pub(crate) struct DefaultReactor {
    slot: AtomicUsize,
    poll: Poll,
    heartbeat: Heartbeat,
    slots: HashMap<Token, SocketStateHandle>,
    handle: DefaultReactorHandle,
}

#[derive(Clone)]
pub(crate) struct DefaultReactorHandle(Arc<AtomicBool>);

impl Reactor for DefaultReactor {
    fn handle(&self) -> Box<dyn ReactorHandle + Send> {
        Box::new(self.handle.clone())
    }

    fn register(&mut self, socket: &mut TcpStream, socket_state: SocketStateHandle) -> Result<()> {
        let token = Token(self.slot.fetch_add(1, Ordering::SeqCst));
        self.poll
            .registry()
            .register(socket, token, Interest::READABLE | Interest::WRITABLE)?;
        self.slots.insert(token, socket_state);
        Ok(())
    }

    fn start(mut self: Box<Self>) -> Result<ThreadHandle> {
        Ok(ThreadHandle::new(
            ThreadBuilder::new()
                .name("lapin-reactor".into())
                .spawn(move || {
                    let mut events = Events::with_capacity(16);
                    while self.should_run() {
                        self.run(&mut events)?;
                    }
                    Ok(())
                })?,
        ))
    }
}

impl DefaultReactor {
    fn new(heartbeat: Heartbeat) -> Result<Self> {
        Ok(Self {
            slot: AtomicUsize::new(1),
            poll: Poll::new()?,
            heartbeat,
            slots: HashMap::default(),
            handle: DefaultReactorHandle(Arc::new(AtomicBool::new(true))),
        })
    }

    fn should_run(&self) -> bool {
        self.handle.0.load(Ordering::SeqCst)
    }

    fn run(&mut self, events: &mut Events) -> Result<()> {
        trace!("reactor poll");
        self.poll.poll(events, self.heartbeat.poll_timeout()?)?;
        trace!("reactor poll done");
        for event in events.iter() {
            if let Some(socket) = self.slots.get(&event.token()) {
                if event.is_error() {
                    socket.send(SocketEvent::Error);
                }
                if event.is_read_closed() || event.is_write_closed() {
                    socket.send(SocketEvent::Closed);
                }
                if event.is_readable() {
                    socket.send(SocketEvent::Readable);
                }
                if event.is_writable() {
                    socket.send(SocketEvent::Writable);
                }
            }
        }
        Ok(())
    }
}

impl ReactorHandle for DefaultReactorHandle {
    fn shutdown(&self) {
        self.0.store(false, Ordering::SeqCst);
    }
}

impl fmt::Debug for DefaultReactorBuilder {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DefaultReactorBuilder").finish()
    }
}

impl fmt::Debug for DefaultReactor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DefaultReactor").finish()
    }
}

#[derive(Clone)]
struct DummyHandle;

impl ReactorHandle for DummyHandle {
    fn shutdown(&self) {}
}
<commit_msg>add more methods to ReatorHandle<commit_after>use crate::{
    heartbeat::Heartbeat,
    socket_state::{SocketEvent, SocketStateHandle},
    tcp::TcpStream,
    thread::ThreadHandle,
    Result,
};
use log::trace;
use mio::{Events, Interest, Poll, Token};
use std::{
    collections::HashMap,
    fmt,
    sync::{
        atomic::{AtomicBool, AtomicUsize, Ordering},
        Arc,
    },
    thread::Builder as ThreadBuilder,
};

pub trait ReactorBuilder: fmt::Debug + Send + Sync {
    fn build(&self, heartbeat: Heartbeat) -> Result<Box<dyn Reactor + Send>>;
}

pub trait Reactor: fmt::Debug + Send {
    fn register(&mut self, socket: &mut TcpStream, socket_state: SocketStateHandle) -> Result<()>;
    fn handle(&self) -> Box<dyn ReactorHandle + Send> {
        Box::new(DummyHandle)
    }
    fn start(self: Box<Self>) -> Result<ThreadHandle> {
        Ok(ThreadHandle::default())
    }
}

pub trait ReactorHandle {
    fn shutdown(&self) {}
    fn start_heartbeat(&self) {}
    fn poll_read(&self) {}
    fn poll_write(&self) {}
}

pub(crate) struct DefaultReactorBuilder;

impl ReactorBuilder for DefaultReactorBuilder {
    fn build(&self, heartbeat: Heartbeat) -> Result<Box<dyn Reactor + Send>> {
        Ok(Box::new(DefaultReactor::new(heartbeat)?))
    }
}

pub(crate) struct DefaultReactor {
    slot: AtomicUsize,
    poll: Poll,
    heartbeat: Heartbeat,
    slots: HashMap<Token, SocketStateHandle>,
    handle: DefaultReactorHandle,
}

#[derive(Clone)]
pub(crate) struct DefaultReactorHandle(Arc<AtomicBool>);

impl Reactor for DefaultReactor {
    fn handle(&self) -> Box<dyn ReactorHandle + Send> {
        Box::new(self.handle.clone())
    }

    fn register(&mut self, socket: &mut TcpStream, socket_state: SocketStateHandle) -> Result<()> {
        let token = Token(self.slot.fetch_add(1, Ordering::SeqCst));
        self.poll
            .registry()
            .register(socket, token, Interest::READABLE | Interest::WRITABLE)?;
        self.slots.insert(token, socket_state);
        Ok(())
    }

    fn start(mut self: Box<Self>) -> Result<ThreadHandle> {
        Ok(ThreadHandle::new(
            ThreadBuilder::new()
                .name("lapin-reactor".into())
                .spawn(move || {
                    let mut events = Events::with_capacity(16);
                    while self.should_run() {
                        self.run(&mut events)?;
                    }
                    Ok(())
                })?,
        ))
    }
}

impl DefaultReactor {
    fn new(heartbeat: Heartbeat) -> Result<Self> {
        Ok(Self {
            slot: AtomicUsize::new(1),
            poll: Poll::new()?,
            heartbeat,
            slots: HashMap::default(),
            handle: DefaultReactorHandle(Arc::new(AtomicBool::new(true))),
        })
    }

    fn should_run(&self) -> bool {
        self.handle.0.load(Ordering::SeqCst)
    }

    fn run(&mut self, events: &mut Events) -> Result<()> {
        trace!("reactor poll");
        self.poll.poll(events, self.heartbeat.poll_timeout()?)?;
        trace!("reactor poll done");
        for event in events.iter() {
            if let Some(socket) = self.slots.get(&event.token()) {
                if event.is_error() {
                    socket.send(SocketEvent::Error);
                }
                if event.is_read_closed() || event.is_write_closed() {
                    socket.send(SocketEvent::Closed);
                }
                if event.is_readable() {
                    socket.send(SocketEvent::Readable);
                }
                if event.is_writable() {
                    socket.send(SocketEvent::Writable);
                }
            }
        }
        Ok(())
    }
}

impl ReactorHandle for DefaultReactorHandle {
    fn shutdown(&self) {
        self.0.store(false, Ordering::SeqCst);
    }
}

impl fmt::Debug for DefaultReactorBuilder {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DefaultReactorBuilder").finish()
    }
}

impl fmt::Debug for DefaultReactor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DefaultReactor").finish()
    }
}

#[derive(Clone)]
struct DummyHandle;

impl ReactorHandle for DummyHandle {}
<|endoftext|>


<commit_before>//! Handle `cargo add` arguments

#![allow(clippy::bool_assert_comparison)]

use cargo_edit::{
    find, get_features_from_registry, get_manifest_from_url, registry_url, workspace_members,
    Dependency,
};
use cargo_edit::{get_latest_dependency, CrateSpec};
use cargo_metadata::Package;
use clap::Parser;
use std::path::PathBuf;

use crate::errors::*;

#[derive(Debug, Parser)]
#[clap(bin_name = "cargo")]
pub enum Command {
    /// Add dependency to a Cargo.toml manifest file.
    #[clap(name = "add")]
    #[clap(after_help = "\
This command allows you to add a dependency to a Cargo.toml manifest file. If <crate> is a github \
or gitlab repository URL, or a local path, `cargo add` will try to automatically get the crate \
name and set the appropriate `--git` or `--path` value.

Please note that Cargo treats versions like '1.2.3' as '^1.2.3' (and that '^1.2.3' is specified \
as '>=1.2.3 and <2.0.0'). By default, `cargo add` will use this format, as it is the one that the \
crates.io registry suggests. One goal of `cargo add` is to prevent you from using wildcard \
dependencies (version set to '*').")]
    Add(Args),
}

#[derive(Debug, Parser)]
#[clap(about, version)]
pub struct Args {
    /// Crates to be added.
    #[clap(value_name = "CRATE", required = true)]
    pub crates: Vec<String>,

    /// Rename a dependency in Cargo.toml,
    /// https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml.
    /// Only works when specifying a single dependency.
    #[clap(long, short)]
    pub rename: Option<String>,

    /// Add crate as development dependency.
    #[clap(long, short = 'D', group = "section")]
    pub dev: bool,

    /// Add crate as build dependency.
    #[clap(long, short = 'B', group = "section")]
    pub build: bool,

    /// Add as dependency to the given target platform.
    #[clap(long, forbid_empty_values = true, group = "section")]
    pub target: Option<String>,

    /// Specify the version to grab from the registry(crates.io).
    /// You can also specify version as part of name, e.g
    /// `cargo add bitflags:0.3.2`.
    #[clap(long, value_name = "URI", conflicts_with = "git")]
    pub vers: Option<String>,

    /// Specify a git repository to download the crate from.
    #[clap(long, value_name = "URI", conflicts_with = "vers")]
    pub git: Option<String>,

    /// Specify a git branch to download the crate from.
    #[clap(long, value_name = "BRANCH", requires = "git", group = "git-ref")]
    pub branch: Option<String>,

    /// Specify a git branch to download the crate from.
    #[clap(long, value_name = "TAG", requires = "git", group = "git-ref")]
    pub tag: Option<String>,

    /// Specify a git branch to download the crate from.
    #[clap(long, value_name = "REV", requires = "git", group = "git-ref")]
    pub rev: Option<String>,

    /// Add as an optional dependency (for use in features).
    #[clap(long, conflicts_with = "dev")]
    pub optional: bool,

    /// Path to the manifest to add a dependency to.
    #[clap(
        long,
        value_name = "PATH",
        parse(from_os_str),
        conflicts_with = "pkgid"
    )]
    pub manifest_path: Option<PathBuf>,

    /// Package id of the crate to add this dependency to.
    #[clap(
        long = "package",
        short = 'p',
        value_name = "PKGID",
        conflicts_with = "manifest-path"
    )]
    pub pkgid: Option<String>,

    /// Space-separated list of features to add. For an alternative approach to
    /// enabling features, consider installing the `cargo-feature` utility.
    #[clap(long)]
    pub features: Option<Vec<String>>,

    /// Set `default-features = false` for the added dependency.
    #[clap(long)]
    pub no_default_features: bool,

    /// Do not print any output in case of success.
    #[clap(long)]
    pub quiet: bool,

    /// Run without accessing the network
    #[clap(long)]
    pub offline: bool,

    /// Registry to use
    #[clap(long, conflicts_with = "git")]
    pub registry: Option<String>,
}

fn parse_version_req(s: &str) -> Result<&str> {
    semver::VersionReq::parse(s).chain_err(|| "Invalid dependency version requirement")?;
    Ok(s)
}

impl Args {
    /// Get dependency section
    pub fn get_section(&self) -> Vec<String> {
        if self.dev {
            vec!["dev-dependencies".to_owned()]
        } else if self.build {
            vec!["build-dependencies".to_owned()]
        } else if let Some(ref target) = self.target {
            assert!(!target.is_empty(), "Target specification may not be empty");

            vec![
                "target".to_owned(),
                target.clone(),
                "dependencies".to_owned(),
            ]
        } else {
            vec!["dependencies".to_owned()]
        }
    }

    fn parse_single_dependency(
        &self,
        crate_spec: &str,
        workspace_members: &[Package],
    ) -> Result<Dependency> {
        let crate_spec = CrateSpec::resolve(crate_spec)?;
        let manifest_path = find(&self.manifest_path)?;
        let registry_url = registry_url(&manifest_path, self.registry.as_deref())?;

        let mut dependency = match &crate_spec {
            CrateSpec::PkgId {
                name: _,
                version_req: Some(_),
            } => {
                let mut dependency = crate_spec.to_dependency()?;
                // crate specifier includes a version (e.g. `docopt:0.8`)
                if let Some(ref url) = self.git {
                    let url = url.clone();
                    let version = dependency.version().unwrap().to_string();
                    return Err(ErrorKind::GitUrlWithVersion(url, version).into());
                }

                let features = get_features_from_registry(
                    &dependency.name,
                    dependency
                        .version()
                        .expect("version populated by `parse_as_version`"),
                    &registry_url,
                )?;
                dependency = dependency.set_available_features(features);

                dependency
            }
            CrateSpec::PkgId {
                name,
                version_req: None,
            } => {
                let mut dependency = crate_spec.to_dependency()?;

                if let Some(repo) = &self.git {
                    assert!(self.vers.is_none());
                    assert!(self.registry.is_none());
                    let features = get_manifest_from_url(repo)?
                        .map(|m| m.features())
                        .transpose()?
                        .unwrap_or_else(Vec::new);

                    dependency = dependency
                        .set_git(
                            repo,
                            self.branch.clone(),
                            self.tag.clone(),
                            self.rev.clone(),
                        )
                        .set_available_features(features);
                } else {
                    if let Some(version) = &self.vers {
                        dependency = dependency.set_version(parse_version_req(version)?);
                    }

                    if self.git.is_none() && self.vers.is_none() {
                        // Only special-case workspaces when the user doesn't provide any extra
                        // information, otherwise, trust the user.
                        if let Some(package) = workspace_members.iter().find(|p| p.name == *name) {
                            dependency = dependency.set_path(
                                package
                                    .manifest_path
                                    .parent()
                                    .expect("at least parent dir")
                                    .as_std_path()
                                    .to_owned(),
                            );
                            // dev-dependencies do not need the version populated
                            if !self.dev {
                                let op = "";
                                let v =
                                    format!("{op}{version}", op = op, version = package.version);
                                dependency = dependency.set_version(&v);
                            }
                        } else {
                            dependency = get_latest_dependency(
                                name,
                                false,
                                &manifest_path,
                                Some(&registry_url),
                            )?;
                            let op = "";
                            let v = format!(
                                "{op}{version}",
                                op = op,
                                // If version is unavailable `get_latest_dependency` must have
                                // returned `Err(FetchVersionError::GetVersion)`
                                version = dependency.version().unwrap_or_else(|| unreachable!())
                            );
                            dependency = dependency.set_version(&v);
                        }
                    }
                }

                dependency
            }
            CrateSpec::Path(_) => {
                let mut dependency = crate_spec.to_dependency()?;
                // dev-dependencies do not need the version populated
                if !self.dev {
                    let dep_path = dependency.path().map(ToOwned::to_owned);
                    if let Some(dep_path) = dep_path {
                        if let Some(package) = workspace_members.iter().find(|p| {
                            p.manifest_path.parent().map(|p| p.as_std_path())
                                == Some(dep_path.as_path())
                        }) {
                            let op = "";
                            let v = format!("{op}{version}", op = op, version = package.version);

                            dependency = dependency.set_version(&v);
                        }
                    }
                }
                dependency
            }
        };

        if let Some(registry) = &self.registry {
            dependency = dependency.set_registry(registry);
        }

        Ok(dependency)
    }

    /// Build dependencies from arguments
    pub fn parse_dependencies(
        &self,
        requested_features: Option<Vec<String>>,
    ) -> Result<Vec<Dependency>> {
        let workspace_members = workspace_members(self.manifest_path.as_deref())?;

        if self.crates.len() > 1 && (self.git.is_some() || self.vers.is_some()) {
            return Err(ErrorKind::MultipleCratesWithGitOrPathOrVers.into());
        }

        if self.crates.len() > 1 && self.rename.is_some() {
            return Err(ErrorKind::MultipleCratesWithRename.into());
        }

        if self.crates.len() > 1 && self.features.is_some() {
            return Err(ErrorKind::MultipleCratesWithFeatures.into());
        }

        self.crates
            .iter()
            .map(|crate_spec| {
                self.parse_single_dependency(crate_spec, &workspace_members)
                    .map(|x| {
                        let mut x = x
                            .set_optional(self.optional)
                            .set_features(requested_features.to_owned())
                            .set_default_features(!self.no_default_features);
                        if let Some(ref rename) = self.rename {
                            x = x.set_rename(rename);
                        }
                        x
                    })
            })
            .collect()
    }
}

#[cfg(test)]
impl Default for Args {
    fn default() -> Args {
        Args {
            crates: vec!["demo".to_owned()],
            rename: None,
            dev: false,
            build: false,
            vers: None,
            git: None,
            branch: None,
            tag: None,
            rev: None,
            target: None,
            optional: false,
            manifest_path: None,
            pkgid: None,
            features: None,
            no_default_features: false,
            quiet: false,
            offline: true,
            registry: None,
        }
    }
}

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

    #[test]
    fn test_dependency_parsing() {
        let args = Args {
            vers: Some("0.4.2".to_owned()),
            ..Args::default()
        };

        assert_eq!(
            args.parse_dependencies(None).unwrap(),
            vec![Dependency::new("demo").set_version("0.4.2")]
        );
    }

    #[test]
    #[cfg(feature = "test-external-apis")]
    fn test_repo_as_arg_parsing() {
        let github_url = "https://github.com/killercup/cargo-edit/";
        let args_github = Args {
            crates: vec![github_url.to_owned()],
            ..Args::default()
        };
        assert_eq!(
            args_github.parse_dependencies(None).unwrap(),
            vec![Dependency::new("cargo-edit").set_git(github_url, None)]
        );

        let gitlab_url = "https://gitlab.com/Polly-lang/Polly.git";
        let args_gitlab = Args {
            crates: vec![gitlab_url.to_owned()],
            ..Args::default()
        };
        assert_eq!(
            args_gitlab.parse_dependencies(None).unwrap(),
            vec![Dependency::new("polly").set_git(gitlab_url, None)]
        );
    }

    #[test]
    fn test_path_as_arg_parsing() {
        let self_path = dunce::canonicalize(std::env::current_dir().unwrap()).unwrap();
        let args_path = Args {
            // Hacky to `display` but should generally work
            crates: vec![self_path.display().to_string()],
            ..Args::default()
        };
        assert_eq!(
            args_path.parse_dependencies(None).unwrap()[0]
                .path()
                .unwrap(),
            self_path
        );
    }

    #[test]
    fn verify_app() {
        use clap::IntoApp;
        Command::into_app().debug_assert()
    }
}
<commit_msg>refactor: Focus on pub items<commit_after>//! Handle `cargo add` arguments

#![allow(clippy::bool_assert_comparison)]

use cargo_edit::{
    find, get_features_from_registry, get_manifest_from_url, registry_url, workspace_members,
    Dependency,
};
use cargo_edit::{get_latest_dependency, CrateSpec};
use cargo_metadata::Package;
use clap::Parser;
use std::path::PathBuf;

use crate::errors::*;

#[derive(Debug, Parser)]
#[clap(bin_name = "cargo")]
pub enum Command {
    /// Add dependency to a Cargo.toml manifest file.
    #[clap(name = "add")]
    #[clap(after_help = "\
This command allows you to add a dependency to a Cargo.toml manifest file. If <crate> is a github \
or gitlab repository URL, or a local path, `cargo add` will try to automatically get the crate \
name and set the appropriate `--git` or `--path` value.

Please note that Cargo treats versions like '1.2.3' as '^1.2.3' (and that '^1.2.3' is specified \
as '>=1.2.3 and <2.0.0'). By default, `cargo add` will use this format, as it is the one that the \
crates.io registry suggests. One goal of `cargo add` is to prevent you from using wildcard \
dependencies (version set to '*').")]
    Add(Args),
}

#[derive(Debug, Parser)]
#[clap(about, version)]
pub struct Args {
    /// Crates to be added.
    #[clap(value_name = "CRATE", required = true)]
    pub crates: Vec<String>,

    /// Rename a dependency in Cargo.toml,
    /// https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml.
    /// Only works when specifying a single dependency.
    #[clap(long, short)]
    pub rename: Option<String>,

    /// Add crate as development dependency.
    #[clap(long, short = 'D', group = "section")]
    pub dev: bool,

    /// Add crate as build dependency.
    #[clap(long, short = 'B', group = "section")]
    pub build: bool,

    /// Add as dependency to the given target platform.
    #[clap(long, forbid_empty_values = true, group = "section")]
    pub target: Option<String>,

    /// Specify the version to grab from the registry(crates.io).
    /// You can also specify version as part of name, e.g
    /// `cargo add bitflags:0.3.2`.
    #[clap(long, value_name = "URI", conflicts_with = "git")]
    pub vers: Option<String>,

    /// Specify a git repository to download the crate from.
    #[clap(long, value_name = "URI", conflicts_with = "vers")]
    pub git: Option<String>,

    /// Specify a git branch to download the crate from.
    #[clap(long, value_name = "BRANCH", requires = "git", group = "git-ref")]
    pub branch: Option<String>,

    /// Specify a git branch to download the crate from.
    #[clap(long, value_name = "TAG", requires = "git", group = "git-ref")]
    pub tag: Option<String>,

    /// Specify a git branch to download the crate from.
    #[clap(long, value_name = "REV", requires = "git", group = "git-ref")]
    pub rev: Option<String>,

    /// Add as an optional dependency (for use in features).
    #[clap(long, conflicts_with = "dev")]
    pub optional: bool,

    /// Path to the manifest to add a dependency to.
    #[clap(
        long,
        value_name = "PATH",
        parse(from_os_str),
        conflicts_with = "pkgid"
    )]
    pub manifest_path: Option<PathBuf>,

    /// Package id of the crate to add this dependency to.
    #[clap(
        long = "package",
        short = 'p',
        value_name = "PKGID",
        conflicts_with = "manifest-path"
    )]
    pub pkgid: Option<String>,

    /// Space-separated list of features to add. For an alternative approach to
    /// enabling features, consider installing the `cargo-feature` utility.
    #[clap(long)]
    pub features: Option<Vec<String>>,

    /// Set `default-features = false` for the added dependency.
    #[clap(long)]
    pub no_default_features: bool,

    /// Do not print any output in case of success.
    #[clap(long)]
    pub quiet: bool,

    /// Run without accessing the network
    #[clap(long)]
    pub offline: bool,

    /// Registry to use
    #[clap(long, conflicts_with = "git")]
    pub registry: Option<String>,
}

fn parse_version_req(s: &str) -> Result<&str> {
    semver::VersionReq::parse(s).chain_err(|| "Invalid dependency version requirement")?;
    Ok(s)
}

impl Args {
    /// Get dependency section
    pub fn get_section(&self) -> Vec<String> {
        if self.dev {
            vec!["dev-dependencies".to_owned()]
        } else if self.build {
            vec!["build-dependencies".to_owned()]
        } else if let Some(ref target) = self.target {
            assert!(!target.is_empty(), "Target specification may not be empty");

            vec![
                "target".to_owned(),
                target.clone(),
                "dependencies".to_owned(),
            ]
        } else {
            vec!["dependencies".to_owned()]
        }
    }

    /// Build dependencies from arguments
    pub fn parse_dependencies(
        &self,
        requested_features: Option<Vec<String>>,
    ) -> Result<Vec<Dependency>> {
        let workspace_members = workspace_members(self.manifest_path.as_deref())?;

        if self.crates.len() > 1 && (self.git.is_some() || self.vers.is_some()) {
            return Err(ErrorKind::MultipleCratesWithGitOrPathOrVers.into());
        }

        if self.crates.len() > 1 && self.rename.is_some() {
            return Err(ErrorKind::MultipleCratesWithRename.into());
        }

        if self.crates.len() > 1 && self.features.is_some() {
            return Err(ErrorKind::MultipleCratesWithFeatures.into());
        }

        self.crates
            .iter()
            .map(|crate_spec| {
                self.parse_single_dependency(crate_spec, &workspace_members)
                    .map(|x| {
                        let mut x = x
                            .set_optional(self.optional)
                            .set_features(requested_features.to_owned())
                            .set_default_features(!self.no_default_features);
                        if let Some(ref rename) = self.rename {
                            x = x.set_rename(rename);
                        }
                        x
                    })
            })
            .collect()
    }

    fn parse_single_dependency(
        &self,
        crate_spec: &str,
        workspace_members: &[Package],
    ) -> Result<Dependency> {
        let crate_spec = CrateSpec::resolve(crate_spec)?;
        let manifest_path = find(&self.manifest_path)?;
        let registry_url = registry_url(&manifest_path, self.registry.as_deref())?;

        let mut dependency = match &crate_spec {
            CrateSpec::PkgId {
                name: _,
                version_req: Some(_),
            } => {
                let mut dependency = crate_spec.to_dependency()?;
                // crate specifier includes a version (e.g. `docopt:0.8`)
                if let Some(ref url) = self.git {
                    let url = url.clone();
                    let version = dependency.version().unwrap().to_string();
                    return Err(ErrorKind::GitUrlWithVersion(url, version).into());
                }

                let features = get_features_from_registry(
                    &dependency.name,
                    dependency
                        .version()
                        .expect("version populated by `parse_as_version`"),
                    &registry_url,
                )?;
                dependency = dependency.set_available_features(features);

                dependency
            }
            CrateSpec::PkgId {
                name,
                version_req: None,
            } => {
                let mut dependency = crate_spec.to_dependency()?;

                if let Some(repo) = &self.git {
                    assert!(self.vers.is_none());
                    assert!(self.registry.is_none());
                    let features = get_manifest_from_url(repo)?
                        .map(|m| m.features())
                        .transpose()?
                        .unwrap_or_else(Vec::new);

                    dependency = dependency
                        .set_git(
                            repo,
                            self.branch.clone(),
                            self.tag.clone(),
                            self.rev.clone(),
                        )
                        .set_available_features(features);
                } else {
                    if let Some(version) = &self.vers {
                        dependency = dependency.set_version(parse_version_req(version)?);
                    }

                    if self.git.is_none() && self.vers.is_none() {
                        // Only special-case workspaces when the user doesn't provide any extra
                        // information, otherwise, trust the user.
                        if let Some(package) = workspace_members.iter().find(|p| p.name == *name) {
                            dependency = dependency.set_path(
                                package
                                    .manifest_path
                                    .parent()
                                    .expect("at least parent dir")
                                    .as_std_path()
                                    .to_owned(),
                            );
                            // dev-dependencies do not need the version populated
                            if !self.dev {
                                let op = "";
                                let v =
                                    format!("{op}{version}", op = op, version = package.version);
                                dependency = dependency.set_version(&v);
                            }
                        } else {
                            dependency = get_latest_dependency(
                                name,
                                false,
                                &manifest_path,
                                Some(&registry_url),
                            )?;
                            let op = "";
                            let v = format!(
                                "{op}{version}",
                                op = op,
                                // If version is unavailable `get_latest_dependency` must have
                                // returned `Err(FetchVersionError::GetVersion)`
                                version = dependency.version().unwrap_or_else(|| unreachable!())
                            );
                            dependency = dependency.set_version(&v);
                        }
                    }
                }

                dependency
            }
            CrateSpec::Path(_) => {
                let mut dependency = crate_spec.to_dependency()?;
                // dev-dependencies do not need the version populated
                if !self.dev {
                    let dep_path = dependency.path().map(ToOwned::to_owned);
                    if let Some(dep_path) = dep_path {
                        if let Some(package) = workspace_members.iter().find(|p| {
                            p.manifest_path.parent().map(|p| p.as_std_path())
                                == Some(dep_path.as_path())
                        }) {
                            let op = "";
                            let v = format!("{op}{version}", op = op, version = package.version);

                            dependency = dependency.set_version(&v);
                        }
                    }
                }
                dependency
            }
        };

        if let Some(registry) = &self.registry {
            dependency = dependency.set_registry(registry);
        }

        Ok(dependency)
    }
}

#[cfg(test)]
impl Default for Args {
    fn default() -> Args {
        Args {
            crates: vec!["demo".to_owned()],
            rename: None,
            dev: false,
            build: false,
            vers: None,
            git: None,
            branch: None,
            tag: None,
            rev: None,
            target: None,
            optional: false,
            manifest_path: None,
            pkgid: None,
            features: None,
            no_default_features: false,
            quiet: false,
            offline: true,
            registry: None,
        }
    }
}

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

    #[test]
    fn test_dependency_parsing() {
        let args = Args {
            vers: Some("0.4.2".to_owned()),
            ..Args::default()
        };

        assert_eq!(
            args.parse_dependencies(None).unwrap(),
            vec![Dependency::new("demo").set_version("0.4.2")]
        );
    }

    #[test]
    #[cfg(feature = "test-external-apis")]
    fn test_repo_as_arg_parsing() {
        let github_url = "https://github.com/killercup/cargo-edit/";
        let args_github = Args {
            crates: vec![github_url.to_owned()],
            ..Args::default()
        };
        assert_eq!(
            args_github.parse_dependencies(None).unwrap(),
            vec![Dependency::new("cargo-edit").set_git(github_url, None)]
        );

        let gitlab_url = "https://gitlab.com/Polly-lang/Polly.git";
        let args_gitlab = Args {
            crates: vec![gitlab_url.to_owned()],
            ..Args::default()
        };
        assert_eq!(
            args_gitlab.parse_dependencies(None).unwrap(),
            vec![Dependency::new("polly").set_git(gitlab_url, None)]
        );
    }

    #[test]
    fn test_path_as_arg_parsing() {
        let self_path = dunce::canonicalize(std::env::current_dir().unwrap()).unwrap();
        let args_path = Args {
            // Hacky to `display` but should generally work
            crates: vec![self_path.display().to_string()],
            ..Args::default()
        };
        assert_eq!(
            args_path.parse_dependencies(None).unwrap()[0]
                .path()
                .unwrap(),
            self_path
        );
    }

    #[test]
    fn verify_app() {
        use clap::IntoApp;
        Command::into_app().debug_assert()
    }
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::{sys, SBData, SBStream, SBTarget};
use std::ffi::{CStr, CString};
use std::fmt;

#[allow(missing_docs)]
pub struct SBSection {
    /// The underlying raw `SBSectionRef`.
    pub raw: sys::SBSectionRef,
}

impl SBSection {
    /// Construct a new `SBSection`.
    pub(crate) fn wrap(raw: sys::SBSectionRef) -> SBSection {
        SBSection { raw }
    }

    /// Construct a new `Some(SBSection)` or `None`.
    pub(crate) fn maybe_wrap(raw: sys::SBSectionRef) -> Option<SBSection> {
        if unsafe { sys::SBSectionIsValid(raw) } {
            Some(SBSection { raw })
        } else {
            None
        }
    }

    /// Check whether or not this is a valid `SBSection` value.
    pub fn is_valid(&self) -> bool {
        unsafe { sys::SBSectionIsValid(self.raw) }
    }

    /// The section name.
    pub fn name(&self) -> &str {
        unsafe {
            match CStr::from_ptr(sys::SBSectionGetName(self.raw)).to_str() {
                Ok(s) => s,
                _ => panic!("Invalid string?"),
            }
        }
    }

    /// The section parent, if there is one.
    pub fn parent(&self) -> Option<SBSection> {
        SBSection::maybe_wrap(unsafe { sys::SBSectionGetParent(self.raw) })
    }

    #[allow(missing_docs)]
    pub fn find_subsection(&self, name: &str) -> Option<SBSection> {
        let name = CString::new(name).unwrap();
        SBSection::maybe_wrap(unsafe { sys::SBSectionFindSubSection(self.raw, name.as_ptr()) })
    }

    /// Get an iterator over the [subsections] known to this section instance.
    ///
    /// [subsections]: SBSection
    pub fn subsections(&self) -> SBSectionSubSectionIter {
        SBSectionSubSectionIter {
            section: self,
            idx: 0,
        }
    }

    #[allow(missing_docs)]
    pub fn file_address(&self) -> u64 {
        unsafe { sys::SBSectionGetFileAddress(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn load_address(&self, target: &SBTarget) -> u64 {
        unsafe { sys::SBSectionGetLoadAddress(self.raw, target.raw) }
    }

    #[allow(missing_docs)]
    pub fn byte_size(&self) -> u64 {
        unsafe { sys::SBSectionGetByteSize(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn file_offset(&self) -> u64 {
        unsafe { sys::SBSectionGetFileOffset(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn file_byte_size(&self) -> u64 {
        unsafe { sys::SBSectionGetFileByteSize(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn section_data(&self) -> SBData {
        SBData::wrap(unsafe { sys::SBSectionGetSectionData(self.raw) })
    }

    #[allow(missing_docs)]
    pub fn section_data_slice(&self, offset: u64, size: u64) -> SBData {
        SBData::wrap(unsafe { sys::SBSectionGetSectionData2(self.raw, offset, size) })
    }

    #[allow(missing_docs)]
    pub fn section_type(&self) -> sys::SectionType {
        unsafe { sys::SBSectionGetSectionType(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn target_byte_size(&self) -> u32 {
        unsafe { sys::SBSectionGetTargetByteSize(self.raw) }
    }
}

/// Iterate over the [subsections] in a [section].
///
/// [subsections]: SBSection
/// [section]: SBSection
pub struct SBSectionSubSectionIter<'d> {
    section: &'d SBSection,
    idx: usize,
}

impl<'d> Iterator for SBSectionSubSectionIter<'d> {
    type Item = SBSection;

    fn next(&mut self) -> Option<SBSection> {
        if self.idx < unsafe { sys::SBSectionGetNumSubSections(self.section.raw) } {
            let r = Some(SBSection::wrap(unsafe {
                sys::SBSectionGetSubSectionAtIndex(self.section.raw, self.idx)
            }));
            self.idx += 1;
            r
        } else {
            None
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let sz = unsafe { sys::SBSectionGetNumSubSections(self.section.raw) };
        (sz - self.idx, Some(sz))
    }
}

impl<'d> ExactSizeIterator for SBSectionSubSectionIter<'d> {}

impl Clone for SBSection {
    fn clone(&self) -> SBSection {
        SBSection {
            raw: unsafe { sys::CloneSBSection(self.raw) },
        }
    }
}

impl fmt::Debug for SBSection {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let stream = SBStream::new();
        unsafe { sys::SBSectionGetDescription(self.raw, stream.raw) };
        write!(fmt, "SBSection {{ {} }}", stream.data())
    }
}

impl Drop for SBSection {
    fn drop(&mut self) {
        unsafe { sys::DisposeSBSection(self.raw) };
    }
}

unsafe impl Send for SBSection {}
unsafe impl Sync for SBSection {}

#[cfg(feature = "graphql")]
#[graphql_object]
impl SBSection {
    fn name() -> &str {
        self.name()
    }

    fn subsections() -> Vec<SBSection> {
        self.subsections().collect()
    }

    fn file_address() -> i32 {
        self.file_address() as i32
    }

    fn byte_size() -> i32 {
        self.byte_size() as i32
    }

    fn file_offset() -> i32 {
        self.file_offset() as i32
    }

    fn file_byte_size() -> i32 {
        self.file_byte_size() as i32
    }

    fn target_byte_size() -> i32 {
        self.target_byte_size() as i32
    }
}
<commit_msg>SBSection: Add binding for permissions, docs for struct.<commit_after>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::{sys, Permissions, SBData, SBStream, SBTarget};
use std::ffi::{CStr, CString};
use std::fmt;

/// Represents an executable image section.
///
/// `SBSection` supports iteration through its [subsections],
/// represented as `SBSection` as well.
///
/// See also:
///
/// - [`SBModule`](crate::SBModule)
/// - [`SBModule::find_section()`](crate::SBModule::find_section)
/// - [`SBModule::sections()`](crate::SBModule::sections)
///
/// [subsections]: SBSection::subsections
pub struct SBSection {
    /// The underlying raw `SBSectionRef`.
    pub raw: sys::SBSectionRef,
}

impl SBSection {
    /// Construct a new `SBSection`.
    pub(crate) fn wrap(raw: sys::SBSectionRef) -> SBSection {
        SBSection { raw }
    }

    /// Construct a new `Some(SBSection)` or `None`.
    pub(crate) fn maybe_wrap(raw: sys::SBSectionRef) -> Option<SBSection> {
        if unsafe { sys::SBSectionIsValid(raw) } {
            Some(SBSection { raw })
        } else {
            None
        }
    }

    /// Check whether or not this is a valid `SBSection` value.
    pub fn is_valid(&self) -> bool {
        unsafe { sys::SBSectionIsValid(self.raw) }
    }

    /// The section name.
    pub fn name(&self) -> &str {
        unsafe {
            match CStr::from_ptr(sys::SBSectionGetName(self.raw)).to_str() {
                Ok(s) => s,
                _ => panic!("Invalid string?"),
            }
        }
    }

    /// The section parent, if there is one.
    pub fn parent(&self) -> Option<SBSection> {
        SBSection::maybe_wrap(unsafe { sys::SBSectionGetParent(self.raw) })
    }

    #[allow(missing_docs)]
    pub fn find_subsection(&self, name: &str) -> Option<SBSection> {
        let name = CString::new(name).unwrap();
        SBSection::maybe_wrap(unsafe { sys::SBSectionFindSubSection(self.raw, name.as_ptr()) })
    }

    /// Get an iterator over the [subsections] known to this section instance.
    ///
    /// [subsections]: SBSection
    pub fn subsections(&self) -> SBSectionSubSectionIter {
        SBSectionSubSectionIter {
            section: self,
            idx: 0,
        }
    }

    #[allow(missing_docs)]
    pub fn file_address(&self) -> u64 {
        unsafe { sys::SBSectionGetFileAddress(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn load_address(&self, target: &SBTarget) -> u64 {
        unsafe { sys::SBSectionGetLoadAddress(self.raw, target.raw) }
    }

    #[allow(missing_docs)]
    pub fn byte_size(&self) -> u64 {
        unsafe { sys::SBSectionGetByteSize(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn file_offset(&self) -> u64 {
        unsafe { sys::SBSectionGetFileOffset(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn file_byte_size(&self) -> u64 {
        unsafe { sys::SBSectionGetFileByteSize(self.raw) }
    }

    #[allow(missing_docs)]
    pub fn section_data(&self) -> SBData {
        SBData::wrap(unsafe { sys::SBSectionGetSectionData(self.raw) })
    }

    #[allow(missing_docs)]
    pub fn section_data_slice(&self, offset: u64, size: u64) -> SBData {
        SBData::wrap(unsafe { sys::SBSectionGetSectionData2(self.raw, offset, size) })
    }

    #[allow(missing_docs)]
    pub fn section_type(&self) -> sys::SectionType {
        unsafe { sys::SBSectionGetSectionType(self.raw) }
    }

    /// Gets the permissions (RWX) of the section of the object file.
    ///
    /// `None` is returned for sections without permissions. Invalid
    /// permissions bits are truncated.
    pub fn permissions(&self) -> Option<Permissions> {
        let perms = unsafe { sys::SBSectionGetPermissions(self.raw) };
        if perms != 0 {
            Some(Permissions::from_bits_truncate(perms))
        } else {
            None
        }
    }

    #[allow(missing_docs)]
    pub fn target_byte_size(&self) -> u32 {
        unsafe { sys::SBSectionGetTargetByteSize(self.raw) }
    }
}

/// Iterate over the [subsections] in a [section].
///
/// [subsections]: SBSection
/// [section]: SBSection
pub struct SBSectionSubSectionIter<'d> {
    section: &'d SBSection,
    idx: usize,
}

impl<'d> Iterator for SBSectionSubSectionIter<'d> {
    type Item = SBSection;

    fn next(&mut self) -> Option<SBSection> {
        if self.idx < unsafe { sys::SBSectionGetNumSubSections(self.section.raw) } {
            let r = Some(SBSection::wrap(unsafe {
                sys::SBSectionGetSubSectionAtIndex(self.section.raw, self.idx)
            }));
            self.idx += 1;
            r
        } else {
            None
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let sz = unsafe { sys::SBSectionGetNumSubSections(self.section.raw) };
        (sz - self.idx, Some(sz))
    }
}

impl<'d> ExactSizeIterator for SBSectionSubSectionIter<'d> {}

impl Clone for SBSection {
    fn clone(&self) -> SBSection {
        SBSection {
            raw: unsafe { sys::CloneSBSection(self.raw) },
        }
    }
}

impl fmt::Debug for SBSection {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let stream = SBStream::new();
        unsafe { sys::SBSectionGetDescription(self.raw, stream.raw) };
        write!(fmt, "SBSection {{ {} }}", stream.data())
    }
}

impl Drop for SBSection {
    fn drop(&mut self) {
        unsafe { sys::DisposeSBSection(self.raw) };
    }
}

unsafe impl Send for SBSection {}
unsafe impl Sync for SBSection {}

#[cfg(feature = "graphql")]
#[graphql_object]
impl SBSection {
    fn name() -> &str {
        self.name()
    }

    fn subsections() -> Vec<SBSection> {
        self.subsections().collect()
    }

    fn file_address() -> i32 {
        self.file_address() as i32
    }

    fn byte_size() -> i32 {
        self.byte_size() as i32
    }

    fn file_offset() -> i32 {
        self.file_offset() as i32
    }

    fn file_byte_size() -> i32 {
        self.file_byte_size() as i32
    }

    fn target_byte_size() -> i32 {
        self.target_byte_size() as i32
    }
}
<|endoftext|>