<commit_before>use std::collections::HashMap;
use std::fs::{create_dir_all, File};
use std::io::{prelude::*, BufReader};
use std::path::{Path, PathBuf};

use log::info;
use serde_derive::{Deserialize, Serialize};
use shellexpand::tilde;

use crate::error::Error;
use crate::setting_defaults::*;

/// All settings which are used by both, the client and the daemon
#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)]
pub struct Shared {
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The directory that is used for all of pueue's state. \
    /// I.e. task logs, state dumps, etc.
    pub pueue_directory: Option<PathBuf>,
    /// The location where runtime related files will be placed.
    /// Defaults to `pueue_directory` unless `$XDG_RUNTIME_DIR` is set.
    pub runtime_directory: Option<PathBuf>,
    /// If this is set to true, unix sockets will be used.
    /// Otherwise we default to TCP+TLS
    #[cfg(not(target_os = "windows"))]
    #[serde(default = "default_true")]
    pub use_unix_socket: bool,
    /// The path where the daemon's PID is located.
    /// This is by default in `runtime_directory/pueue.pid`.
    pub pid_path: Option<PathBuf>,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the unix socket.
    #[cfg(not(target_os = "windows"))]
    pub unix_socket_path: Option<PathBuf>,

    /// The TCP hostname/ip address.
    #[serde(default = "default_host")]
    pub host: String,
    /// The TCP port.
    #[serde(default = "default_port")]
    pub port: String,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS certificate used by the daemon. \
    /// This is also used by the client to verify the daemon's identity.
    pub daemon_cert: Option<PathBuf>,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS key used by the daemon.
    pub daemon_key: Option<PathBuf>,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the file containing the shared secret used to authenticate the client.
    pub shared_secret_path: Option<PathBuf>,
}

/// All settings which are used by the client
#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)]
pub struct Client {
    /// If set to true, all tasks will be restart in place, instead of creating a new task.
    /// False is the default, as you'll lose the logs of the previously failed tasks when
    /// restarting tasks in place.
    #[serde(default = "Default::default")]
    pub restart_in_place: bool,
    /// Whether the client should read the logs directly from disk or whether it should
    /// request the data from the daemon via socket.
    #[serde(default = "default_true")]
    pub read_local_logs: bool,
    /// Whether the client should show a confirmation question on potential dangerous actions.
    #[serde(default = "Default::default")]
    pub show_confirmation_questions: bool,
    /// Whether aliases specified in `pueue_aliases.yml` should be expanded in the `pueue status`
    /// or shown in their short form.
    #[serde(default = "Default::default")]
    pub show_expanded_aliases: bool,
    /// Whether the client should use dark shades instead of regular colors.
    #[serde(default = "Default::default")]
    pub dark_mode: bool,
    /// The max amount of lines each task get's in the `pueue status` view.
    pub max_status_lines: Option<usize>,
    /// The format that will be used to display time formats in `pueue status`.
    #[serde(default = "default_status_time_format")]
    pub status_time_format: String,
    /// The format that will be used to display datetime formats in `pueue status`.
    #[serde(default = "default_status_datetime_format")]
    pub status_datetime_format: String,
}

/// All settings which are used by the daemon
#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)]
pub struct Daemon {
    /// Whether a group should be paused as soon as a single task fails
    #[serde(default = "Default::default")]
    pub pause_group_on_failure: bool,
    /// Whether the daemon (and all groups) should be paused as soon as a single task fails
    #[serde(default = "Default::default")]
    pub pause_all_on_failure: bool,
    /// The callback that's called whenever a task finishes.
    pub callback: Option<String>,
    /// The amount of log lines from stdout/stderr that are passed to the callback command.
    #[serde(default = "default_callback_log_lines")]
    pub callback_log_lines: usize,
    /// The legacy configuration for groups
    #[serde(skip_serializing)]
    #[deprecated(
        since = "1.1.0",
        note = "The configuration for groups is now stored in the state."
    )]
    pub groups: Option<HashMap<String, i64>>,
}

impl Default for Settings {
    fn default() -> Self {
        Settings {
            client: Client {
                read_local_logs: true,
                status_time_format: default_status_time_format(),
                status_datetime_format: default_status_datetime_format(),
                ..Default::default()
            },
            daemon: Daemon {
                callback_log_lines: default_callback_log_lines(),
                ..Default::default()
            },
            shared: Shared {
                #[cfg(not(target_os = "windows"))]
                use_unix_socket: true,
                host: default_host(),
                port: default_port(),
                ..Default::default()
            },
            profiles: HashMap::new(),
        }
    }
}

/// The parent settings struct. \
/// This contains all other setting structs.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Settings {
    #[serde(default = "Default::default")]
    pub client: Client,
    #[serde(default = "Default::default")]
    pub daemon: Daemon,
    pub shared: Shared,
    #[serde(default = "HashMap::new")]
    pub profiles: HashMap<String, NestedSettings>,
}

/// The nested settings struct for profiles. \
/// In contrast to the normal `Settings` struct, this struct doesn't allow profiles.
/// That way we prevent nested profiles and problems with self-referencing structs.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct NestedSettings {
    #[serde(default = "Default::default")]
    pub client: Client,
    #[serde(default = "Default::default")]
    pub daemon: Daemon,
    #[serde(default = "Default::default")]
    pub shared: Shared,
}

/// Get the default config directory.
/// If no config can be found, fallback to the current directory.
pub fn configuration_directories() -> Vec<PathBuf> {
    if let Some(config_dir) = dirs::config_dir() {
        vec![config_dir.join("pueue.yml"), PathBuf::from(".")]
    } else {
        vec![PathBuf::from(".")]
    }
}

/// Little helper which expands a given path's `~` characters to a fully qualified path.
pub fn expand_home(old_path: &Path) -> PathBuf {
    PathBuf::from(tilde(&old_path.to_string_lossy()).into_owned())
}

impl Shared {
    pub fn pueue_directory(&self) -> PathBuf {
        if let Some(path) = &self.pueue_directory {
            expand_home(path)
        } else if let Some(path) = dirs::data_local_dir() {
            path.join("pueue")
        } else {
            PathBuf::from("./pueue")
        }
    }

    /// Get the current runtime directory in the following precedence.
    /// 1. Config value
    /// 2. Environment configuration
    /// 3. Pueue directory
    pub fn runtime_directory(&self) -> PathBuf {
        if let Some(path) = &self.runtime_directory {
            expand_home(path)
        } else if let Some(path) = dirs::runtime_dir() {
            path
        } else {
            self.pueue_directory()
        }
    }

    /// The unix socket path can either be explicitly specified or it's simply placed in the
    /// current runtime directory.
    #[cfg(not(target_os = "windows"))]
    pub fn unix_socket_path(&self) -> PathBuf {
        if let Some(path) = &self.unix_socket_path {
            expand_home(path)
        } else {
            self.runtime_directory()
                .join(format!("pueue_{}.socket", whoami::username()))
        }
    }

    /// The daemon's pid path can either be explicitly specified or it's simply placed in the
    /// current runtime directory.
    pub fn pid_path(&self) -> PathBuf {
        if let Some(path) = &self.pid_path {
            expand_home(path)
        } else {
            self.runtime_directory().join("pueue.pid")
        }
    }

    pub fn daemon_cert(&self) -> PathBuf {
        if let Some(path) = &self.daemon_cert {
            expand_home(path)
        } else {
            self.pueue_directory().join("certs").join("daemon.cert")
        }
    }

    pub fn daemon_key(&self) -> PathBuf {
        if let Some(path) = &self.daemon_key {
            expand_home(path)
        } else {
            self.pueue_directory().join("certs").join("daemon.key")
        }
    }

    pub fn shared_secret_path(&self) -> PathBuf {
        if let Some(path) = &self.shared_secret_path {
            expand_home(path)
        } else {
            self.pueue_directory().join("shared_secret")
        }
    }
}

impl Settings {
    /// Try to read existing config files, while using default values for non-existing fields.
    /// If successful, this will return a full config as well as a boolean on whether we found an
    /// existing configuration file or not.
    ///
    /// The default local config locations depends on the current target.
    pub fn read(from_file: &Option<PathBuf>) -> Result<(Settings, bool), Error> {
        // Load the config from a very specific file path
        if let Some(path) = from_file {
            // Open the file in read-only mode with buffer.
            let file = File::open(path)
                .map_err(|err| Error::IoPathError(path.clone(), "opening config file", err))?;
            let reader = BufReader::new(file);

            let settings = serde_yaml::from_reader(reader)
                .map_err(|err| Error::ConfigDeserialization(err.to_string()))?;
            return Ok((settings, true));
        };

        info!("Parsing config files");

        let config_dirs = configuration_directories();
        for directory in config_dirs.into_iter() {
            let path = directory.join("pueue.yml");
            info!("Checking path: {path:?}");

            // Check if the file exists and parse it.
            if path.exists() && path.is_file() {
                info!("Found config file at: {path:?}");

                // Open the file in read-only mode with buffer.
                let file = File::open(&path)
                    .map_err(|err| Error::IoPathError(path, "opening config file.", err))?;
                let reader = BufReader::new(file);

                let settings = serde_yaml::from_reader(reader)
                    .map_err(|err| Error::ConfigDeserialization(err.to_string()))?;
                return Ok((settings, true));
            }
        }

        info!("No config file found. Use default config.");
        // Return a default configuration if we couldn't find a file.
        Ok((Settings::default(), false))
    }

    /// Save the current configuration as a file to the given path. \
    /// If no path is given, the default configuration path will be used. \
    /// The file is then written to the main configuration directory of the respective OS.
    pub fn save(&self, path: &Option<PathBuf>) -> Result<(), Error> {
        let config_path = if let Some(path) = path {
            path.clone()
        } else if let Some(path) = dirs::config_dir() {
            let path = path.join("pueue");
            path.join("pueue.yml")
        } else {
            return Err(Error::Generic(
                "Failed to resolve default config directory. User home cannot be determined."
                    .into(),
            ));
        };
        let config_dir = config_path
            .parent()
            .ok_or_else(|| Error::InvalidPath("Couldn't resolve config directory".into()))?;

        // Create the config dir, if it doesn't exist yet
        if !config_dir.exists() {
            create_dir_all(&config_dir).map_err(|err| {
                Error::IoPathError(config_dir.to_path_buf(), "creating config dir", err)
            })?;
        }

        let content = match serde_yaml::to_string(self) {
            Ok(content) => content,
            Err(error) => {
                return Err(Error::Generic(format!(
                    "Configuration file serialization failed:\n{error}"
                )))
            }
        };
        let mut file = File::create(&config_path).map_err(|err| {
            Error::IoPathError(config_dir.to_path_buf(), "creating settings file", err)
        })?;
        file.write_all(content.as_bytes()).map_err(|err| {
            Error::IoPathError(config_dir.to_path_buf(), "writing settings file", err)
        })?;

        Ok(())
    }

    /// Try to load a profile. Error if it doesn't exist.
    pub fn load_profile(&mut self, profile: &str) -> Result<(), Error> {
        let profile = self.profiles.remove(profile).ok_or_else(|| {
            Error::ConfigDeserialization(format!("Couldn't find profile with name \"{profile}\""))
        })?;

        self.client = profile.client;
        self.daemon = profile.daemon;
        self.shared = profile.shared;

        Ok(())
    }
}

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

    /// Check if profiles get loaded correctly.
    #[test]
    fn test_load_profile() {
        // Create some default settings and ensure that default values are loaded.
        let mut settings = Settings::default();
        assert_eq!(
            settings.client.status_time_format,
            default_status_time_format()
        );
        assert_eq!(
            settings.daemon.callback_log_lines,
            default_callback_log_lines()
        );
        assert_eq!(settings.shared.host, default_host());

        // Crate a new profile with slightly different values.
        let mut profile = Settings::default();
        profile.client.status_time_format = "test".to_string();
        profile.daemon.callback_log_lines = 100_000;
        profile.shared.host = "quatschhost".to_string();
        let profile = NestedSettings {
            client: profile.client,
            daemon: profile.daemon,
            shared: profile.shared,
        };

        settings.profiles.insert("testprofile".to_string(), profile);

        // Load the profile and ensure the new values are now loaded.
        settings
            .load_profile("testprofile")
            .expect("We just added the profile");

        assert_eq!(settings.client.status_time_format, "test");
        assert_eq!(settings.daemon.callback_log_lines, 100_000);
        assert_eq!(settings.shared.host, "quatschhost");
    }

    /// A proper pueue [Error] should be thrown if the profile cannot be found.
    #[test]
    fn test_error_on_missing_profile() {
        let mut settings = Settings::default();

        let result = settings.load_profile("doesn't exist");
        let expected_error_message = "Couldn't find profile with name \"doesn't exist\"";
        if let Err(Error::ConfigDeserialization(error_message)) = result {
            assert_eq!(error_message, expected_error_message);
            return;
        }

        panic!("Got unexpected result when expecting missing profile error: {result:?}");
    }
}
<commit_msg>Fix default config directory path<commit_after>use std::collections::HashMap;
use std::fs::{create_dir_all, File};
use std::io::{prelude::*, BufReader};
use std::path::{Path, PathBuf};

use log::info;
use serde_derive::{Deserialize, Serialize};
use shellexpand::tilde;

use crate::error::Error;
use crate::setting_defaults::*;

/// All settings which are used by both, the client and the daemon
#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)]
pub struct Shared {
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The directory that is used for all of pueue's state. \
    /// I.e. task logs, state dumps, etc.
    pub pueue_directory: Option<PathBuf>,
    /// The location where runtime related files will be placed.
    /// Defaults to `pueue_directory` unless `$XDG_RUNTIME_DIR` is set.
    pub runtime_directory: Option<PathBuf>,
    /// If this is set to true, unix sockets will be used.
    /// Otherwise we default to TCP+TLS
    #[cfg(not(target_os = "windows"))]
    #[serde(default = "default_true")]
    pub use_unix_socket: bool,
    /// The path where the daemon's PID is located.
    /// This is by default in `runtime_directory/pueue.pid`.
    pub pid_path: Option<PathBuf>,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the unix socket.
    #[cfg(not(target_os = "windows"))]
    pub unix_socket_path: Option<PathBuf>,

    /// The TCP hostname/ip address.
    #[serde(default = "default_host")]
    pub host: String,
    /// The TCP port.
    #[serde(default = "default_port")]
    pub port: String,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS certificate used by the daemon. \
    /// This is also used by the client to verify the daemon's identity.
    pub daemon_cert: Option<PathBuf>,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS key used by the daemon.
    pub daemon_key: Option<PathBuf>,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the file containing the shared secret used to authenticate the client.
    pub shared_secret_path: Option<PathBuf>,
}

/// All settings which are used by the client
#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)]
pub struct Client {
    /// If set to true, all tasks will be restart in place, instead of creating a new task.
    /// False is the default, as you'll lose the logs of the previously failed tasks when
    /// restarting tasks in place.
    #[serde(default = "Default::default")]
    pub restart_in_place: bool,
    /// Whether the client should read the logs directly from disk or whether it should
    /// request the data from the daemon via socket.
    #[serde(default = "default_true")]
    pub read_local_logs: bool,
    /// Whether the client should show a confirmation question on potential dangerous actions.
    #[serde(default = "Default::default")]
    pub show_confirmation_questions: bool,
    /// Whether aliases specified in `pueue_aliases.yml` should be expanded in the `pueue status`
    /// or shown in their short form.
    #[serde(default = "Default::default")]
    pub show_expanded_aliases: bool,
    /// Whether the client should use dark shades instead of regular colors.
    #[serde(default = "Default::default")]
    pub dark_mode: bool,
    /// The max amount of lines each task get's in the `pueue status` view.
    pub max_status_lines: Option<usize>,
    /// The format that will be used to display time formats in `pueue status`.
    #[serde(default = "default_status_time_format")]
    pub status_time_format: String,
    /// The format that will be used to display datetime formats in `pueue status`.
    #[serde(default = "default_status_datetime_format")]
    pub status_datetime_format: String,
}

/// All settings which are used by the daemon
#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)]
pub struct Daemon {
    /// Whether a group should be paused as soon as a single task fails
    #[serde(default = "Default::default")]
    pub pause_group_on_failure: bool,
    /// Whether the daemon (and all groups) should be paused as soon as a single task fails
    #[serde(default = "Default::default")]
    pub pause_all_on_failure: bool,
    /// The callback that's called whenever a task finishes.
    pub callback: Option<String>,
    /// The amount of log lines from stdout/stderr that are passed to the callback command.
    #[serde(default = "default_callback_log_lines")]
    pub callback_log_lines: usize,
    /// The legacy configuration for groups
    #[serde(skip_serializing)]
    #[deprecated(
        since = "1.1.0",
        note = "The configuration for groups is now stored in the state."
    )]
    pub groups: Option<HashMap<String, i64>>,
}

impl Default for Settings {
    fn default() -> Self {
        Settings {
            client: Client {
                read_local_logs: true,
                status_time_format: default_status_time_format(),
                status_datetime_format: default_status_datetime_format(),
                ..Default::default()
            },
            daemon: Daemon {
                callback_log_lines: default_callback_log_lines(),
                ..Default::default()
            },
            shared: Shared {
                #[cfg(not(target_os = "windows"))]
                use_unix_socket: true,
                host: default_host(),
                port: default_port(),
                ..Default::default()
            },
            profiles: HashMap::new(),
        }
    }
}

/// The parent settings struct. \
/// This contains all other setting structs.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Settings {
    #[serde(default = "Default::default")]
    pub client: Client,
    #[serde(default = "Default::default")]
    pub daemon: Daemon,
    pub shared: Shared,
    #[serde(default = "HashMap::new")]
    pub profiles: HashMap<String, NestedSettings>,
}

/// The nested settings struct for profiles. \
/// In contrast to the normal `Settings` struct, this struct doesn't allow profiles.
/// That way we prevent nested profiles and problems with self-referencing structs.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct NestedSettings {
    #[serde(default = "Default::default")]
    pub client: Client,
    #[serde(default = "Default::default")]
    pub daemon: Daemon,
    #[serde(default = "Default::default")]
    pub shared: Shared,
}

/// Get the default config directory.
/// If no config can be found, fallback to the current directory.
pub fn configuration_directories() -> Vec<PathBuf> {
    if let Some(config_dir) = dirs::config_dir() {
        vec![config_dir.join("pueue"), PathBuf::from(".")]
    } else {
        vec![PathBuf::from(".")]
    }
}

/// Little helper which expands a given path's `~` characters to a fully qualified path.
pub fn expand_home(old_path: &Path) -> PathBuf {
    PathBuf::from(tilde(&old_path.to_string_lossy()).into_owned())
}

impl Shared {
    pub fn pueue_directory(&self) -> PathBuf {
        if let Some(path) = &self.pueue_directory {
            expand_home(path)
        } else if let Some(path) = dirs::data_local_dir() {
            path.join("pueue")
        } else {
            PathBuf::from("./pueue")
        }
    }

    /// Get the current runtime directory in the following precedence.
    /// 1. Config value
    /// 2. Environment configuration
    /// 3. Pueue directory
    pub fn runtime_directory(&self) -> PathBuf {
        if let Some(path) = &self.runtime_directory {
            expand_home(path)
        } else if let Some(path) = dirs::runtime_dir() {
            path
        } else {
            self.pueue_directory()
        }
    }

    /// The unix socket path can either be explicitly specified or it's simply placed in the
    /// current runtime directory.
    #[cfg(not(target_os = "windows"))]
    pub fn unix_socket_path(&self) -> PathBuf {
        if let Some(path) = &self.unix_socket_path {
            expand_home(path)
        } else {
            self.runtime_directory()
                .join(format!("pueue_{}.socket", whoami::username()))
        }
    }

    /// The daemon's pid path can either be explicitly specified or it's simply placed in the
    /// current runtime directory.
    pub fn pid_path(&self) -> PathBuf {
        if let Some(path) = &self.pid_path {
            expand_home(path)
        } else {
            self.runtime_directory().join("pueue.pid")
        }
    }

    pub fn daemon_cert(&self) -> PathBuf {
        if let Some(path) = &self.daemon_cert {
            expand_home(path)
        } else {
            self.pueue_directory().join("certs").join("daemon.cert")
        }
    }

    pub fn daemon_key(&self) -> PathBuf {
        if let Some(path) = &self.daemon_key {
            expand_home(path)
        } else {
            self.pueue_directory().join("certs").join("daemon.key")
        }
    }

    pub fn shared_secret_path(&self) -> PathBuf {
        if let Some(path) = &self.shared_secret_path {
            expand_home(path)
        } else {
            self.pueue_directory().join("shared_secret")
        }
    }
}

impl Settings {
    /// Try to read existing config files, while using default values for non-existing fields.
    /// If successful, this will return a full config as well as a boolean on whether we found an
    /// existing configuration file or not.
    ///
    /// The default local config locations depends on the current target.
    pub fn read(from_file: &Option<PathBuf>) -> Result<(Settings, bool), Error> {
        // Load the config from a very specific file path
        if let Some(path) = from_file {
            // Open the file in read-only mode with buffer.
            let file = File::open(path)
                .map_err(|err| Error::IoPathError(path.clone(), "opening config file", err))?;
            let reader = BufReader::new(file);

            let settings = serde_yaml::from_reader(reader)
                .map_err(|err| Error::ConfigDeserialization(err.to_string()))?;
            return Ok((settings, true));
        };

        info!("Parsing config files");

        let config_dirs = configuration_directories();
        for directory in config_dirs.into_iter() {
            let path = directory.join("pueue.yml");
            info!("Checking path: {path:?}");

            // Check if the file exists and parse it.
            if path.exists() && path.is_file() {
                info!("Found config file at: {path:?}");

                // Open the file in read-only mode with buffer.
                let file = File::open(&path)
                    .map_err(|err| Error::IoPathError(path, "opening config file.", err))?;
                let reader = BufReader::new(file);

                let settings = serde_yaml::from_reader(reader)
                    .map_err(|err| Error::ConfigDeserialization(err.to_string()))?;
                return Ok((settings, true));
            }
        }

        info!("No config file found. Use default config.");
        // Return a default configuration if we couldn't find a file.
        Ok((Settings::default(), false))
    }

    /// Save the current configuration as a file to the given path. \
    /// If no path is given, the default configuration path will be used. \
    /// The file is then written to the main configuration directory of the respective OS.
    pub fn save(&self, path: &Option<PathBuf>) -> Result<(), Error> {
        let config_path = if let Some(path) = path {
            path.clone()
        } else if let Some(path) = dirs::config_dir() {
            let path = path.join("pueue");
            path.join("pueue.yml")
        } else {
            return Err(Error::Generic(
                "Failed to resolve default config directory. User home cannot be determined."
                    .into(),
            ));
        };
        let config_dir = config_path
            .parent()
            .ok_or_else(|| Error::InvalidPath("Couldn't resolve config directory".into()))?;

        // Create the config dir, if it doesn't exist yet
        if !config_dir.exists() {
            create_dir_all(&config_dir).map_err(|err| {
                Error::IoPathError(config_dir.to_path_buf(), "creating config dir", err)
            })?;
        }

        let content = match serde_yaml::to_string(self) {
            Ok(content) => content,
            Err(error) => {
                return Err(Error::Generic(format!(
                    "Configuration file serialization failed:\n{error}"
                )))
            }
        };
        let mut file = File::create(&config_path).map_err(|err| {
            Error::IoPathError(config_dir.to_path_buf(), "creating settings file", err)
        })?;
        file.write_all(content.as_bytes()).map_err(|err| {
            Error::IoPathError(config_dir.to_path_buf(), "writing settings file", err)
        })?;

        Ok(())
    }

    /// Try to load a profile. Error if it doesn't exist.
    pub fn load_profile(&mut self, profile: &str) -> Result<(), Error> {
        let profile = self.profiles.remove(profile).ok_or_else(|| {
            Error::ConfigDeserialization(format!("Couldn't find profile with name \"{profile}\""))
        })?;

        self.client = profile.client;
        self.daemon = profile.daemon;
        self.shared = profile.shared;

        Ok(())
    }
}

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

    /// Check if profiles get loaded correctly.
    #[test]
    fn test_load_profile() {
        // Create some default settings and ensure that default values are loaded.
        let mut settings = Settings::default();
        assert_eq!(
            settings.client.status_time_format,
            default_status_time_format()
        );
        assert_eq!(
            settings.daemon.callback_log_lines,
            default_callback_log_lines()
        );
        assert_eq!(settings.shared.host, default_host());

        // Crate a new profile with slightly different values.
        let mut profile = Settings::default();
        profile.client.status_time_format = "test".to_string();
        profile.daemon.callback_log_lines = 100_000;
        profile.shared.host = "quatschhost".to_string();
        let profile = NestedSettings {
            client: profile.client,
            daemon: profile.daemon,
            shared: profile.shared,
        };

        settings.profiles.insert("testprofile".to_string(), profile);

        // Load the profile and ensure the new values are now loaded.
        settings
            .load_profile("testprofile")
            .expect("We just added the profile");

        assert_eq!(settings.client.status_time_format, "test");
        assert_eq!(settings.daemon.callback_log_lines, 100_000);
        assert_eq!(settings.shared.host, "quatschhost");
    }

    /// A proper pueue [Error] should be thrown if the profile cannot be found.
    #[test]
    fn test_error_on_missing_profile() {
        let mut settings = Settings::default();

        let result = settings.load_profile("doesn't exist");
        let expected_error_message = "Couldn't find profile with name \"doesn't exist\"";
        if let Err(Error::ConfigDeserialization(error_message)) = result {
            assert_eq!(error_message, expected_error_message);
            return;
        }

        panic!("Got unexpected result when expecting missing profile error: {result:?}");
    }
}
<|endoftext|>


<commit_before>extern crate futures;
extern crate hyper;
extern crate regex;

#[macro_use] extern crate lazy_static;

use futures::future::FutureResult;
use hyper::Method;
use hyper::server::{Http, Request, Response, Service};
use regex::Regex;

struct HelloWorld;

impl Service for HelloWorld {
    // boilerplate hooking up hyper's server types
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    // The future representing the eventual Response your call will
    // resolve to. This can change to whatever Future you need.
    type Future = FutureResult<Self::Response, Self::Error>;

    fn call(&self, req: Request) -> Self::Future {
        lazy_static! {
            static ref GREETING_RE: Regex = Regex::new(r"^/greeting/([a-z]+)$").unwrap();
        }
        let mut response = Response::new();

        match (req.method(), req.path()) {
            (&Method::Get, "/") => {
                response.set_body("Hello World!");
            },
            _ => {
                let cap = GREETING_RE.captures(req.path()).unwrap();
                response.set_body(format!("Hello, {}", cap.get(1).unwrap().as_str()));
            }
        };
        futures::future::ok(response)
    }
}

fn main() {
    let addr = "127.0.0.1:3000".parse().unwrap();
    let server = Http::new().bind(&addr, || Ok(HelloWorld)).unwrap();
    server.run().unwrap();
}
<commit_msg>Improve Rust/hyper readability<commit_after>extern crate futures;
extern crate hyper;
extern crate regex;

#[macro_use] extern crate lazy_static;

use futures::future::FutureResult;
use hyper::server::{Http, Request, Response, Service};
use regex::Regex;

struct HelloWorld;

impl Service for HelloWorld {
    // boilerplate hooking up hyper's server types
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = FutureResult<Self::Response, Self::Error>;

    fn call(&self, req: Request) -> Self::Future {
        futures::future::ok(
            match req.path() {
                "/" => {
                    Response::new().with_body("Hello World!")
                },
                path => {
                    lazy_static! {
                        static ref GREETING_RE: Regex = Regex::new(r"^/greeting/([a-z]+)$").unwrap();
                    }
                    let cap = GREETING_RE.captures(path).unwrap();
                    Response::new().with_body(format!("Hello, {}", &cap[1]))
                }
            }
        )
    }
}

fn main() {
    let addr = "127.0.0.1:3000".parse().unwrap();
    let server = Http::new().bind(&addr, || Ok(HelloWorld)).unwrap();
    server.run().unwrap();
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate bitutils;
#[macro_use]
extern crate error_chain;
extern crate extprim;
extern crate indextree;
#[macro_use]
extern crate log;
extern crate mio;
extern crate openssl;
extern crate parking_lot;

#[macro_use]
pub mod utils;

mod clock;
mod cpu;
pub mod dbgcore;
pub mod gdbstub;
pub mod hwcore;
pub mod io;
pub mod ldr;
pub mod msgs;
mod mem;
<commit_msg>lib: Make remaining modules public<commit_after>#[macro_use]
extern crate bitutils;
#[macro_use]
extern crate error_chain;
extern crate extprim;
extern crate indextree;
#[macro_use]
extern crate log;
extern crate mio;
extern crate openssl;
extern crate parking_lot;

#[macro_use]
pub mod utils;

pub mod clock;
pub mod cpu;
pub mod dbgcore;
pub mod gdbstub;
pub mod hwcore;
pub mod io;
pub mod ldr;
pub mod msgs;
pub mod mem;
<|endoftext|>


<commit_before>// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This test case tests the incremental compilation hash (ICH) implementation
// for `while let` loops.

// The general pattern followed here is: Change one thing between rev1 and rev2
// and make sure that the hash has changed, then change nothing between rev2 and
// rev3 and make sure that the hash has not changed.

// must-compile-successfully
// revisions: cfail1 cfail2 cfail3
// compile-flags: -Z query-dep-graph

#![allow(warnings)]
#![feature(rustc_attrs)]
#![crate_type="rlib"]

// Change loop body ------------------------------------------------------------
#[cfg(cfail1)]
fn change_loop_body() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn change_loop_body() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 2;
        break;
    }
}

// Change loop body ------------------------------------------------------------
#[cfg(cfail1)]
fn change_loop_condition() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn change_loop_condition() {
    let mut _x = 0;
    while let Some(1u32) = None {
        _x = 1;
        break;
    }
}

// Add break -------------------------------------------------------------------
#[cfg(cfail1)]
fn add_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn add_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

// Add loop label --------------------------------------------------------------
#[cfg(cfail1)]
fn add_loop_label() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn add_loop_label() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

// Add loop label to break -----------------------------------------------------
#[cfg(cfail1)]
fn add_loop_label_to_break() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn add_loop_label_to_break() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        break 'label;
    }
}

// Change break label ----------------------------------------------------------
#[cfg(cfail1)]
fn change_break_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            break 'inner;
        }
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn change_break_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            break 'outer;
        }
    }
}

// Add loop label to continue --------------------------------------------------
#[cfg(cfail1)]
fn add_loop_label_to_continue() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        continue;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn add_loop_label_to_continue() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        continue 'label;
    }
}

// Change continue label ----------------------------------------------------------
#[cfg(cfail1)]
fn change_continue_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            continue 'inner;
        }
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn change_continue_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            continue 'outer;
        }
    }
}

// Change continue to break ----------------------------------------------------
#[cfg(cfail1)]
fn change_continue_to_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        continue;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(label="Hir", cfg="cfail2")]
#[rustc_clean(label="Hir", cfg="cfail3")]
#[rustc_dirty(label="HirBody", cfg="cfail2")]
#[rustc_clean(label="HirBody", cfg="cfail3")]
fn change_continue_to_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}
<commit_msg>Update 'while let loop' fingerprint hash tests<commit_after>// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This test case tests the incremental compilation hash (ICH) implementation
// for `while let` loops.

// The general pattern followed here is: Change one thing between rev1 and rev2
// and make sure that the hash has changed, then change nothing between rev2 and
// rev3 and make sure that the hash has not changed.

// must-compile-successfully
// revisions: cfail1 cfail2 cfail3
// compile-flags: -Z query-dep-graph

#![allow(warnings)]
#![feature(rustc_attrs)]
#![crate_type="rlib"]

// Change loop body ------------------------------------------------------------
#[cfg(cfail1)]
pub fn change_loop_body() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody, MirValidated, MirOptimized")]
#[rustc_clean(cfg="cfail3")]
pub fn change_loop_body() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 2;
        break;
    }
}

// Change loop body ------------------------------------------------------------
#[cfg(cfail1)]
pub fn change_loop_condition() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody, MirValidated, MirOptimized")]
#[rustc_clean(cfg="cfail3")]
pub fn change_loop_condition() {
    let mut _x = 0;
    while let Some(1u32) = None {
        _x = 1;
        break;
    }
}

// Add break -------------------------------------------------------------------
#[cfg(cfail1)]
pub fn add_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody, MirValidated, MirOptimized, TypeckTables")]
#[rustc_clean(cfg="cfail3")]
pub fn add_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

// Add loop label --------------------------------------------------------------
#[cfg(cfail1)]
pub fn add_loop_label() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody")]
#[rustc_clean(cfg="cfail3")]
pub fn add_loop_label() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

// Add loop label to break -----------------------------------------------------
#[cfg(cfail1)]
pub fn add_loop_label_to_break() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        break;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody")]
#[rustc_clean(cfg="cfail3")]
pub fn add_loop_label_to_break() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        break 'label;
    }
}

// Change break label ----------------------------------------------------------
#[cfg(cfail1)]
pub fn change_break_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            break 'inner;
        }
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody, MirValidated, MirOptimized, TypeckTables")]
#[rustc_clean(cfg="cfail3")]
pub fn change_break_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            break 'outer;
        }
    }
}

// Add loop label to continue --------------------------------------------------
#[cfg(cfail1)]
pub fn add_loop_label_to_continue() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        continue;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody")]
#[rustc_clean(cfg="cfail3")]
pub fn add_loop_label_to_continue() {
    let mut _x = 0;
    'label: while let Some(0u32) = None {
        _x = 1;
        continue 'label;
    }
}

// Change continue label ----------------------------------------------------------
#[cfg(cfail1)]
pub fn change_continue_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            continue 'inner;
        }
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody, MirValidated, MirOptimized, TypeckTables")]
#[rustc_clean(cfg="cfail3")]
pub fn change_continue_label() {
    let mut _x = 0;
    'outer: while let Some(0u32) = None {
        'inner: while let Some(0u32) = None {
            _x = 1;
            continue 'outer;
        }
    }
}

// Change continue to break ----------------------------------------------------
#[cfg(cfail1)]
pub fn change_continue_to_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        continue;
    }
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="HirBody, MirValidated, MirOptimized")]
#[rustc_clean(cfg="cfail3")]
pub fn change_continue_to_break() {
    let mut _x = 0;
    while let Some(0u32) = None {
        _x = 1;
        break;
    }
}
<|endoftext|>


<commit_before>// run-pass
// needs-asm-support
// only-x86_64

// Checks that multiple clobber_abi options can be used

#![feature(asm)]

extern "sysv64" fn foo(x: i32) -> i32 {
    x + 16
}

extern "win64" fn bar(x: i32) -> i32 {
    x / 2
}

fn main() {
    let x = 8;
    let y: i32;
    // call `foo` with `x` as the input, and then `bar` with the output of `foo`
    // and output that to `y`
    unsafe {
        asm!(
            "call {}; mov rcx, rax; call {}",
            sym foo,
            sym bar,
            in("rdi") x,
            out("rax") y,
            clobber_abi("sysv64", "win64"),
        );
    }
    assert_eq!((x, y), (8, 12));
}
<commit_msg>add missing sym feature<commit_after>// run-pass
// needs-asm-support
// only-x86_64

// Checks that multiple clobber_abi options can be used

#![feature(asm, asm_sym)]

extern "sysv64" fn foo(x: i32) -> i32 {
    x + 16
}

extern "win64" fn bar(x: i32) -> i32 {
    x / 2
}

fn main() {
    let x = 8;
    let y: i32;
    // call `foo` with `x` as the input, and then `bar` with the output of `foo`
    // and output that to `y`
    unsafe {
        asm!(
            "call {}; mov rcx, rax; call {}",
            sym foo,
            sym bar,
            in("rdi") x,
            out("rax") y,
            clobber_abi("sysv64", "win64"),
        );
    }
    assert_eq!((x, y), (8, 12));
}
<|endoftext|>


<commit_before>// Copyright 2017 Serde Developers
//
// 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 std::collections::HashSet;

use syn::{self, visit};
use syn::punctuated::Punctuated;

use internals::ast::{Data, Container};
use internals::attr;

use proc_macro2::Span;

// Remove the default from every type parameter because in the generated impls
// they look like associated types: "error: associated type bindings are not
// allowed here".
pub fn without_defaults(generics: &syn::Generics) -> syn::Generics {
    syn::Generics {
        params: generics
            .params
            .iter()
            .map(|param| match *param {
                syn::GenericParam::Type(ref param) => {
                    syn::GenericParam::Type(syn::TypeParam {
                        eq_token: None,
                        default: None,
                        ..param.clone()
                    })
                }
                _ => param.clone(),
            })
            .collect(),
        ..generics.clone()
    }
}

pub fn with_where_predicates(
    generics: &syn::Generics,
    predicates: &[syn::WherePredicate],
) -> syn::Generics {
    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .extend(predicates.into_iter().cloned());
    generics
}

pub fn with_where_predicates_from_fields<F, W>(
    cont: &Container,
    generics: &syn::Generics,
    trait_bound: &syn::Path,
    from_field: F,
    gen_bound_where: W,
) -> syn::Generics
where
    F: Fn(&attr::Field) -> Option<&[syn::WherePredicate]>,
    W: Fn(&attr::Field) -> bool,
{
    let predicates = cont.data
        .all_fields()
        .flat_map(|field| {
            let field_ty = field.ty;
            let matching_generic = |t: &syn::PathSegment, g: &syn::GenericParam| match *g {
                syn::GenericParam::Type(ref generic_ty)
                    if generic_ty.ident == t.ident => true,
                _ => false
            };

            let mut field_bound: Option<syn::WherePredicate> = None;
            if let syn::Type::Path(ref ty_path) = *field_ty {
                field_bound = match (gen_bound_where(&field.attrs), ty_path.path.segments.first()) {
                    (true, Some(syn::punctuated::Pair::Punctuated(ref t, _))) =>
                        if generics.params.iter().any(|g| matching_generic(t, g)) {
                            Some(parse_quote!(#field_ty: #trait_bound))
                        } else {None},
                    (_, _) => None
                };
            }
            field_bound.into_iter().chain(from_field(&field.attrs).into_iter().flat_map(|predicates| predicates.to_vec()))
        });

    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .extend(predicates);
    generics
}

// Puts the given bound on any generic type parameters that are used in fields
// for which filter returns true.
//
// For example, the following struct needs the bound `A: Serialize, B: Serialize`.
//
//     struct S<'b, A, B: 'b, C> {
//         a: A,
//         b: Option<&'b B>
//         #[serde(skip_serializing)]
//         c: C,
//     }
pub fn with_bound<F>(
    cont: &Container,
    generics: &syn::Generics,
    filter: F,
    bound: &syn::Path,
) -> syn::Generics
where
    F: Fn(&attr::Field, Option<&attr::Variant>) -> bool,
{
    struct FindTyParams {
        // Set of all generic type parameters on the current struct (A, B, C in
        // the example). Initialized up front.
        all_ty_params: HashSet<syn::Ident>,
        // Set of generic type parameters used in fields for which filter
        // returns true (A and B in the example). Filled in as the visitor sees
        // them.
        relevant_ty_params: HashSet<syn::Ident>,
    }
    impl<'ast> visit::Visit<'ast> for FindTyParams {
        fn visit_path(&mut self, path: &syn::Path) {
            if let Some(seg) = path.segments.last() {
                if seg.into_value().ident == "PhantomData" {
                    // Hardcoded exception, because PhantomData<T> implements
                    // Serialize and Deserialize whether or not T implements it.
                    return;
                }
            }
            if path.leading_colon.is_none() && path.segments.len() == 1 {
                let id = path.segments[0].ident;
                if self.all_ty_params.contains(&id) {
                    self.relevant_ty_params.insert(id);
                }
            }
            visit::visit_path(self, path);
        }

        // Type parameter should not be considered used by a macro path.
        //
        //     struct TypeMacro<T> {
        //         mac: T!(),
        //         marker: PhantomData<T>,
        //     }
        fn visit_macro(&mut self, _mac: &syn::Macro) {}
    }

    let all_ty_params: HashSet<_> = generics
        .params
        .iter()
        .filter_map(|param| match *param {
            syn::GenericParam::Type(ref param) => Some(param.ident),
            _ => None,
        })
        .collect();

    let mut visitor = FindTyParams {
        all_ty_params: all_ty_params,
        relevant_ty_params: HashSet::new(),
    };
    match cont.data {
        Data::Enum(ref variants) => for variant in variants.iter() {
            let relevant_fields = variant
                .fields
                .iter()
                .filter(|field| filter(&field.attrs, Some(&variant.attrs)));
            for field in relevant_fields {
                visit::visit_type(&mut visitor, field.ty);
            }
        },
        Data::Struct(_, ref fields) => {
            for field in fields.iter().filter(|field| filter(&field.attrs, None)) {
                visit::visit_type(&mut visitor, field.ty);
            }
        }
    }

    let new_predicates = generics
        .params
        .iter()
        .filter_map(|param| match *param {
            syn::GenericParam::Type(ref param) => Some(param.ident),
            _ => None,
        })
        .filter(|id| visitor.relevant_ty_params.contains(id))
        .map(|id| {
            syn::WherePredicate::Type(syn::PredicateType {
                lifetimes: None,
                // the type parameter that is being bounded e.g. T
                bounded_ty: syn::Type::Path(syn::TypePath {
                    qself: None,
                    path: id.into(),
                }),
                colon_token: Default::default(),
                // the bound e.g. Serialize
                bounds: vec![
                    syn::TypeParamBound::Trait(syn::TraitBound {
                        paren_token: None,
                        modifier: syn::TraitBoundModifier::None,
                        lifetimes: None,
                        path: bound.clone(),
                    }),
                ].into_iter().collect(),
            })
        });

    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .extend(new_predicates);
    generics
}

pub fn with_self_bound(
    cont: &Container,
    generics: &syn::Generics,
    bound: &syn::Path,
) -> syn::Generics {
    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .push(syn::WherePredicate::Type(syn::PredicateType {
            lifetimes: None,
            // the type that is being bounded e.g. MyStruct<'a, T>
            bounded_ty: type_of_item(cont),
            colon_token: Default::default(),
            // the bound e.g. Default
            bounds: vec![
                syn::TypeParamBound::Trait(syn::TraitBound {
                    paren_token: None,
                    modifier: syn::TraitBoundModifier::None,
                    lifetimes: None,
                    path: bound.clone(),
                }),
            ].into_iter().collect(),
        }));
    generics
}

pub fn with_lifetime_bound(generics: &syn::Generics, lifetime: &str) -> syn::Generics {
    let bound = syn::Lifetime::new(lifetime, Span::call_site());
    let def = syn::LifetimeDef {
        attrs: Vec::new(),
        lifetime: bound,
        colon_token: None,
        bounds: Punctuated::new(),
    };

    let params = Some(syn::GenericParam::Lifetime(def))
        .into_iter()
        .chain(generics.params
            .iter()
            .cloned()
            .map(|mut param| {
                match param {
                    syn::GenericParam::Lifetime(ref mut param) => {
                        param.bounds.push(bound);
                    }
                    syn::GenericParam::Type(ref mut param) => {
                        param.bounds.push(syn::TypeParamBound::Lifetime(bound));
                    }
                    syn::GenericParam::Const(_) => {}
                }
                param
            }))
        .collect();

    syn::Generics {
        params: params,
        ..generics.clone()
    }
}

fn type_of_item(cont: &Container) -> syn::Type {
    syn::Type::Path(syn::TypePath {
        qself: None,
        path: syn::Path {
            leading_colon: None,
            segments: vec![
                syn::PathSegment {
                    ident: cont.ident,
                    arguments: syn::PathArguments::AngleBracketed(
                        syn::AngleBracketedGenericArguments {
                            colon2_token: None,
                            lt_token: Default::default(),
                            args: cont.generics
                                .params
                                .iter()
                                .map(|param| match *param {
                                    syn::GenericParam::Type(ref param) => {
                                        syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {
                                            qself: None,
                                            path: param.ident.into(),
                                        }))
                                    }
                                    syn::GenericParam::Lifetime(ref param) => {
                                        syn::GenericArgument::Lifetime(param.lifetime)
                                    }
                                    syn::GenericParam::Const(_) => {
                                        panic!("Serde does not support const generics yet");
                                    }
                                })
                                .collect(),
                            gt_token: Default::default(),
                        },
                    ),
                },
            ].into_iter().collect(),
        },
    })
}
<commit_msg>Simplify with_where_predicates_from_fields<commit_after>// Copyright 2017 Serde Developers
//
// 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 std::collections::HashSet;

use syn::{self, visit};
use syn::punctuated::{Punctuated, Pair};

use internals::ast::{Data, Container};
use internals::attr;

use proc_macro2::Span;

// Remove the default from every type parameter because in the generated impls
// they look like associated types: "error: associated type bindings are not
// allowed here".
pub fn without_defaults(generics: &syn::Generics) -> syn::Generics {
    syn::Generics {
        params: generics
            .params
            .iter()
            .map(|param| match *param {
                syn::GenericParam::Type(ref param) => {
                    syn::GenericParam::Type(syn::TypeParam {
                        eq_token: None,
                        default: None,
                        ..param.clone()
                    })
                }
                _ => param.clone(),
            })
            .collect(),
        ..generics.clone()
    }
}

pub fn with_where_predicates(
    generics: &syn::Generics,
    predicates: &[syn::WherePredicate],
) -> syn::Generics {
    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .extend(predicates.into_iter().cloned());
    generics
}

pub fn with_where_predicates_from_fields<F, W>(
    cont: &Container,
    generics: &syn::Generics,
    trait_bound: &syn::Path,
    from_field: F,
    gen_bound_where: W,
) -> syn::Generics
where
    F: Fn(&attr::Field) -> Option<&[syn::WherePredicate]>,
    W: Fn(&attr::Field) -> bool,
{
    let type_params = generics.type_params()
        .map(|param| param.ident)
        .collect::<HashSet<_>>();

    let predicates_from_associated_types = cont.data
        .all_fields()
        .filter(|field| gen_bound_where(&field.attrs))
        .filter_map(|field| {
            if let syn::Type::Path(ref ty) = *field.ty {
                if let Some(Pair::Punctuated(ref t, _)) = ty.path.segments.first() {
                    if type_params.contains(&t.ident) {
                        return Some(parse_quote!(#ty: #trait_bound));
                    }
                }
            }
            None::<syn::WherePredicate>
        });

    let predicates_from_field_attrs = cont.data
        .all_fields()
        .flat_map(|field| from_field(&field.attrs))
        .flat_map(|predicates| predicates.to_vec());

    let mut generics = generics.clone();
    {
        let predicates = &mut generics.make_where_clause().predicates;
        predicates.extend(predicates_from_associated_types);
        predicates.extend(predicates_from_field_attrs);
    }
    generics
}

// Puts the given bound on any generic type parameters that are used in fields
// for which filter returns true.
//
// For example, the following struct needs the bound `A: Serialize, B: Serialize`.
//
//     struct S<'b, A, B: 'b, C> {
//         a: A,
//         b: Option<&'b B>
//         #[serde(skip_serializing)]
//         c: C,
//     }
pub fn with_bound<F>(
    cont: &Container,
    generics: &syn::Generics,
    filter: F,
    bound: &syn::Path,
) -> syn::Generics
where
    F: Fn(&attr::Field, Option<&attr::Variant>) -> bool,
{
    struct FindTyParams {
        // Set of all generic type parameters on the current struct (A, B, C in
        // the example). Initialized up front.
        all_ty_params: HashSet<syn::Ident>,
        // Set of generic type parameters used in fields for which filter
        // returns true (A and B in the example). Filled in as the visitor sees
        // them.
        relevant_ty_params: HashSet<syn::Ident>,
    }
    impl<'ast> visit::Visit<'ast> for FindTyParams {
        fn visit_path(&mut self, path: &syn::Path) {
            if let Some(seg) = path.segments.last() {
                if seg.into_value().ident == "PhantomData" {
                    // Hardcoded exception, because PhantomData<T> implements
                    // Serialize and Deserialize whether or not T implements it.
                    return;
                }
            }
            if path.leading_colon.is_none() && path.segments.len() == 1 {
                let id = path.segments[0].ident;
                if self.all_ty_params.contains(&id) {
                    self.relevant_ty_params.insert(id);
                }
            }
            visit::visit_path(self, path);
        }

        // Type parameter should not be considered used by a macro path.
        //
        //     struct TypeMacro<T> {
        //         mac: T!(),
        //         marker: PhantomData<T>,
        //     }
        fn visit_macro(&mut self, _mac: &syn::Macro) {}
    }

    let all_ty_params: HashSet<_> = generics
        .params
        .iter()
        .filter_map(|param| match *param {
            syn::GenericParam::Type(ref param) => Some(param.ident),
            _ => None,
        })
        .collect();

    let mut visitor = FindTyParams {
        all_ty_params: all_ty_params,
        relevant_ty_params: HashSet::new(),
    };
    match cont.data {
        Data::Enum(ref variants) => for variant in variants.iter() {
            let relevant_fields = variant
                .fields
                .iter()
                .filter(|field| filter(&field.attrs, Some(&variant.attrs)));
            for field in relevant_fields {
                visit::visit_type(&mut visitor, field.ty);
            }
        },
        Data::Struct(_, ref fields) => {
            for field in fields.iter().filter(|field| filter(&field.attrs, None)) {
                visit::visit_type(&mut visitor, field.ty);
            }
        }
    }

    let new_predicates = generics
        .params
        .iter()
        .filter_map(|param| match *param {
            syn::GenericParam::Type(ref param) => Some(param.ident),
            _ => None,
        })
        .filter(|id| visitor.relevant_ty_params.contains(id))
        .map(|id| {
            syn::WherePredicate::Type(syn::PredicateType {
                lifetimes: None,
                // the type parameter that is being bounded e.g. T
                bounded_ty: syn::Type::Path(syn::TypePath {
                    qself: None,
                    path: id.into(),
                }),
                colon_token: Default::default(),
                // the bound e.g. Serialize
                bounds: vec![
                    syn::TypeParamBound::Trait(syn::TraitBound {
                        paren_token: None,
                        modifier: syn::TraitBoundModifier::None,
                        lifetimes: None,
                        path: bound.clone(),
                    }),
                ].into_iter().collect(),
            })
        });

    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .extend(new_predicates);
    generics
}

pub fn with_self_bound(
    cont: &Container,
    generics: &syn::Generics,
    bound: &syn::Path,
) -> syn::Generics {
    let mut generics = generics.clone();
    generics.make_where_clause()
        .predicates
        .push(syn::WherePredicate::Type(syn::PredicateType {
            lifetimes: None,
            // the type that is being bounded e.g. MyStruct<'a, T>
            bounded_ty: type_of_item(cont),
            colon_token: Default::default(),
            // the bound e.g. Default
            bounds: vec![
                syn::TypeParamBound::Trait(syn::TraitBound {
                    paren_token: None,
                    modifier: syn::TraitBoundModifier::None,
                    lifetimes: None,
                    path: bound.clone(),
                }),
            ].into_iter().collect(),
        }));
    generics
}

pub fn with_lifetime_bound(generics: &syn::Generics, lifetime: &str) -> syn::Generics {
    let bound = syn::Lifetime::new(lifetime, Span::call_site());
    let def = syn::LifetimeDef {
        attrs: Vec::new(),
        lifetime: bound,
        colon_token: None,
        bounds: Punctuated::new(),
    };

    let params = Some(syn::GenericParam::Lifetime(def))
        .into_iter()
        .chain(generics.params
            .iter()
            .cloned()
            .map(|mut param| {
                match param {
                    syn::GenericParam::Lifetime(ref mut param) => {
                        param.bounds.push(bound);
                    }
                    syn::GenericParam::Type(ref mut param) => {
                        param.bounds.push(syn::TypeParamBound::Lifetime(bound));
                    }
                    syn::GenericParam::Const(_) => {}
                }
                param
            }))
        .collect();

    syn::Generics {
        params: params,
        ..generics.clone()
    }
}

fn type_of_item(cont: &Container) -> syn::Type {
    syn::Type::Path(syn::TypePath {
        qself: None,
        path: syn::Path {
            leading_colon: None,
            segments: vec![
                syn::PathSegment {
                    ident: cont.ident,
                    arguments: syn::PathArguments::AngleBracketed(
                        syn::AngleBracketedGenericArguments {
                            colon2_token: None,
                            lt_token: Default::default(),
                            args: cont.generics
                                .params
                                .iter()
                                .map(|param| match *param {
                                    syn::GenericParam::Type(ref param) => {
                                        syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {
                                            qself: None,
                                            path: param.ident.into(),
                                        }))
                                    }
                                    syn::GenericParam::Lifetime(ref param) => {
                                        syn::GenericArgument::Lifetime(param.lifetime)
                                    }
                                    syn::GenericParam::Const(_) => {
                                        panic!("Serde does not support const generics yet");
                                    }
                                })
                                .collect(),
                            gt_token: Default::default(),
                        },
                    ),
                },
            ].into_iter().collect(),
        },
    })
}
<|endoftext|>


<commit_before>use std::env;
use std::path::Path;
use rustc_serialize::json;
use rustc_serialize::json::{BuilderError, Json};
use curl::{ErrCode, http};
use curl::http::handle::{Method, Request};
use cargo_edit::Manifest;
use regex::Regex;

const REGISTRY_HOST: &'static str = "https://crates.io";

/// Query latest version from crates.io
///
/// The latest version will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - the response from crates.io is an error or in an incorrect format,
/// - or when a crate with the given name does not exist on crates.io.
pub fn get_latest_version(crate_name: &str) -> Result<String, FetchVersionError> {
    if env::var("CARGO_IS_TEST").is_ok() {
        // We are in a simulated reality. Nothing is real here.
        // FIXME: Use actual test handling code.
        return Ok("CURRENT_VERSION_TEST".into());
    }

    let crate_data = try!(fetch_cratesio(&format!("/crates/{}/versions", crate_name)));
    let crate_json = try!(Json::from_str(&crate_data));

    crate_json.as_object()
        .and_then(|c| c.get("versions"))
        .and_then(|c| c.as_array())
        .and_then(|c| {
            for version in c {
                let version_object = version.as_object();
                let yanked = version_object
                    .and_then(|v| v.get("yanked"))
                    .and_then(|v| v.as_boolean());
                if let Some(false) = yanked {
                    return version_object;
                }
            }
            return None;
        })
        .and_then(|v| v.get("num"))
        .and_then(|v| v.as_string())
        .map(|v| v.to_owned())
        .ok_or(FetchVersionError::GetVersion)
}

quick_error! {
    #[derive(Debug)]
    pub enum FetchVersionError {
        CratesIo(err: CratesIoError) {
            from()
            description("crates.io Error")
            display("crates.io Error: {}", err)
            cause(err)
        }
        Json(err: BuilderError) {
            from()
            description("JSON Error")
            display("Error parsing JSON: {}", err)
            cause(err)
        }
        GetVersion { description("get version error") }
    }
}

// ---
// The following was mostly copied from [1] and is therefore
// (c) 2015 Alex Crichton <alex@alexcrichton.com>
//
// [1]: https://github.com/rust-lang/cargo/blob/bd690d8dff83c7b7714f236a08304ee20732382b/src/crates-io/lib.rs
// ---

fn fetch_cratesio(path: &str) -> Result<String, FetchVersionError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(format!("{}/api/v1{}", REGISTRY_HOST, path))
        .header("Accept", "application/json")
        .content_type("application/json");
    handle_request(req.exec()).map_err(From::from)
}

fn handle_request(response: Result<http::Response, ErrCode>) -> Result<String, CratesIoError> {
    let response = try!(response.map_err(CratesIoError::Curl));
    match response.get_code() {
        0 | 200 => {}
        403 => return Err(CratesIoError::Unauthorized),
        404 => return Err(CratesIoError::NotFound),
        _ => return Err(CratesIoError::NotOkResponse(response)),
    }

    let body = match String::from_utf8(response.move_body()) {
        Ok(body) => body,
        Err(..) => return Err(CratesIoError::NonUtf8Body),
    };

    if let Ok(errors) = json::decode::<ApiErrorList>(&body) {
        return Err(CratesIoError::Api(errors.errors.into_iter().map(|s| s.detail).collect()));
    }

    Ok(body)
}

#[derive(RustcDecodable)]
struct ApiErrorList {
    errors: Vec<ApiError>,
}
#[derive(RustcDecodable)]
struct ApiError {
    detail: String,
}

quick_error! {
    #[derive(Debug)]
    pub enum CratesIoError {
        Curl(e: ErrCode) {}
        NotOkResponse(e: http::Response)  {}
        NonUtf8Body  {}
        Api(e: Vec<String>)  {}
        Unauthorized  {}
        NotFound {}
    }
}

#[cfg_attr(rustfmt, rustfmt_skip)]
quick_error! {
    #[derive(Debug)]
    pub enum FetchGitError {
        FetchGit(err: CratesIoError) {
            from()
            description("fetch error: ")
            display("fetch error: {}", err)
            cause(err)
        }
        ParseRegex { description("parse error: unable to parse git repo url") }
        IncompleteCaptures { description("parse error: the git repo url seems incomplete") }
        LocalCargoToml { description("path error: unable to open Cargo.toml") }
        // RemoteCargoToml { description("path error: unable to open Cargo.toml from the provided repo") }
        ParseCargoToml { description("parse error: unable to parse the external Cargo.toml") }
    }
}

/// Query crate name by accessing a github repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from github is an error or in an incorrect format.
pub fn get_crate_name_from_github(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.at(1), cap.at(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
                                      user,
                                      repo);

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing a gitlab repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from gitlab is an error or in an incorrect format.
pub fn get_crate_name_from_gitlab(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.at(1), cap.at(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://gitlab.com/{}/{}/raw/master/Cargo.toml", user, repo);

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing Cargo.toml in a local path
///
/// The name will be returned as a string. This will fail, when
/// Cargo.toml is not present in the root of the path.
pub fn get_crate_name_from_path(path: &str) -> Result<String, FetchGitError> {
    let cargo_file = Path::new(path).join("Cargo.toml");
    Manifest::open(&cargo_file.to_str())
        .map_err(|_| FetchGitError::LocalCargoToml)
        .and_then(|ref manifest| get_name_from_manifest(manifest))
}

fn get_name_from_manifest(manifest: &Manifest) -> Result<String, FetchGitError> {
    manifest.data
        .get("package")
        .and_then(|m| m.lookup("name"))
        .and_then(|name| name.as_str().map(|s| s.to_string()))
        .ok_or(FetchGitError::ParseCargoToml)
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String, FetchGitError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(url)
        .header("Accept", "text/plain")
        .content_type("text/plain");
    handle_request(req.exec()).map_err(From::from)
}
<commit_msg>Move code to get latest unyanked version into fn<commit_after>use std::env;
use std::path::Path;
use rustc_serialize::json;
use rustc_serialize::json::{BuilderError, Json};
use curl::{ErrCode, http};
use curl::http::handle::{Method, Request};
use cargo_edit::Manifest;
use regex::Regex;

const REGISTRY_HOST: &'static str = "https://crates.io";

/// Query latest version from crates.io
///
/// The latest version will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - the response from crates.io is an error or in an incorrect format,
/// - or when a crate with the given name does not exist on crates.io.
pub fn get_latest_version(crate_name: &str) -> Result<String, FetchVersionError> {
    if env::var("CARGO_IS_TEST").is_ok() {
        // We are in a simulated reality. Nothing is real here.
        // FIXME: Use actual test handling code.
        return Ok("CURRENT_VERSION_TEST".into());
    }

    let crate_data = try!(fetch_cratesio(&format!("/crates/{}/versions", crate_name)));
    let crate_json = try!(Json::from_str(&crate_data));

    read_latest_version(crate_json)
}

/// Read latest version from JSON structure
///
/// Assumes the version are sorted so that the first non-yanked version is the
/// latest, and thus the one we want.
fn read_latest_version(crate_json: Json) -> Result<String, FetchVersionError> {
    crate_json.as_object()
        .and_then(|c| c.get("versions"))
        .and_then(Json::as_array)
        .and_then(|vs| vs.iter()
            .filter_map(Json::as_object)
            .find(|&v| !v.get("yanked").and_then(Json::as_boolean).unwrap_or(true))
        )
        .and_then(|v| v.get("num"))
        .and_then(Json::as_string)
        .map(str::to_owned)
        .ok_or(FetchVersionError::GetVersion)
}

quick_error! {
    #[derive(Debug)]
    pub enum FetchVersionError {
        CratesIo(err: CratesIoError) {
            from()
            description("crates.io Error")
            display("crates.io Error: {}", err)
            cause(err)
        }
        Json(err: BuilderError) {
            from()
            description("JSON Error")
            display("Error parsing JSON: {}", err)
            cause(err)
        }
        GetVersion { description("get version error") }
    }
}

// ---
// The following was mostly copied from [1] and is therefore
// (c) 2015 Alex Crichton <alex@alexcrichton.com>
//
// [1]: https://github.com/rust-lang/cargo/blob/bd690d8dff83c7b7714f236a08304ee20732382b/src/crates-io/lib.rs
// ---

fn fetch_cratesio(path: &str) -> Result<String, FetchVersionError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(format!("{}/api/v1{}", REGISTRY_HOST, path))
        .header("Accept", "application/json")
        .content_type("application/json");
    handle_request(req.exec()).map_err(From::from)
}

fn handle_request(response: Result<http::Response, ErrCode>) -> Result<String, CratesIoError> {
    let response = try!(response.map_err(CratesIoError::Curl));
    match response.get_code() {
        0 | 200 => {}
        403 => return Err(CratesIoError::Unauthorized),
        404 => return Err(CratesIoError::NotFound),
        _ => return Err(CratesIoError::NotOkResponse(response)),
    }

    let body = match String::from_utf8(response.move_body()) {
        Ok(body) => body,
        Err(..) => return Err(CratesIoError::NonUtf8Body),
    };

    if let Ok(errors) = json::decode::<ApiErrorList>(&body) {
        return Err(CratesIoError::Api(errors.errors.into_iter().map(|s| s.detail).collect()));
    }

    Ok(body)
}

#[derive(RustcDecodable)]
struct ApiErrorList {
    errors: Vec<ApiError>,
}
#[derive(RustcDecodable)]
struct ApiError {
    detail: String,
}

quick_error! {
    #[derive(Debug)]
    pub enum CratesIoError {
        Curl(e: ErrCode) {}
        NotOkResponse(e: http::Response)  {}
        NonUtf8Body  {}
        Api(e: Vec<String>)  {}
        Unauthorized  {}
        NotFound {}
    }
}

#[cfg_attr(rustfmt, rustfmt_skip)]
quick_error! {
    #[derive(Debug)]
    pub enum FetchGitError {
        FetchGit(err: CratesIoError) {
            from()
            description("fetch error: ")
            display("fetch error: {}", err)
            cause(err)
        }
        ParseRegex { description("parse error: unable to parse git repo url") }
        IncompleteCaptures { description("parse error: the git repo url seems incomplete") }
        LocalCargoToml { description("path error: unable to open Cargo.toml") }
        // RemoteCargoToml { description("path error: unable to open Cargo.toml from the provided repo") }
        ParseCargoToml { description("parse error: unable to parse the external Cargo.toml") }
    }
}

/// Query crate name by accessing a github repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from github is an error or in an incorrect format.
pub fn get_crate_name_from_github(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.at(1), cap.at(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
                                      user,
                                      repo);

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing a gitlab repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from gitlab is an error or in an incorrect format.
pub fn get_crate_name_from_gitlab(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.at(1), cap.at(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://gitlab.com/{}/{}/raw/master/Cargo.toml", user, repo);

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing Cargo.toml in a local path
///
/// The name will be returned as a string. This will fail, when
/// Cargo.toml is not present in the root of the path.
pub fn get_crate_name_from_path(path: &str) -> Result<String, FetchGitError> {
    let cargo_file = Path::new(path).join("Cargo.toml");
    Manifest::open(&cargo_file.to_str())
        .map_err(|_| FetchGitError::LocalCargoToml)
        .and_then(|ref manifest| get_name_from_manifest(manifest))
}

fn get_name_from_manifest(manifest: &Manifest) -> Result<String, FetchGitError> {
    manifest.data
        .get("package")
        .and_then(|m| m.lookup("name"))
        .and_then(|name| name.as_str().map(|s| s.to_string()))
        .ok_or(FetchGitError::ParseCargoToml)
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String, FetchGitError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(url)
        .header("Accept", "text/plain")
        .content_type("text/plain");
    handle_request(req.exec()).map_err(From::from)
}
<|endoftext|>