<commit_before>use cliargsparser::CliArgsParser;
use dirs;
use gettextrs::gettext;
use libc;
use logger::{self, Level};
use std::fs::{self, DirBuilder};
use std::io;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};
use utils;
use xdg;

pub const NEWSBOAT_SUBDIR_XDG: &str = "newsboat";
pub const NEWSBOAT_CONFIG_SUBDIR: &str = ".newsboat";
pub const NEWSBEUTER_SUBDIR_XDG: &str = "newsbeuter";
pub const NEWSBEUTER_CONFIG_SUBDIR: &str = ".newsbeuter";
pub const LOCK_SUFFIX: &str = ".lock";

const URLS_FILENAME: &str = "urls";
const CACHE_FILENAME: &str = "cache.db";
const CONFIG_FILENAME: &str = "config";
const QUEUE_FILENAME: &str = "queue";
const SEARCH_HISTORY_FILENAME: &str = "history.search";
const CMDLINE_HISTORY_FILENAME: &str = "history.cmdline";

#[derive(Debug)]
pub struct ConfigPaths {
    env_home: PathBuf,
    error_message: String,

    data_dir: PathBuf,
    config_dir: PathBuf,

    url_file: PathBuf,
    cache_file: PathBuf,
    config_file: PathBuf,
    lock_file: PathBuf,
    queue_file: PathBuf,
    search_file: PathBuf,
    cmdline_file: PathBuf,

    silent: bool,
    using_nonstandard_configs: bool,
}

impl ConfigPaths {
    pub fn new() -> ConfigPaths {
        let mut config_paths = ConfigPaths {
            env_home: PathBuf::new(),
            error_message: String::new(),

            data_dir: PathBuf::new(),
            config_dir: PathBuf::new(),

            url_file: PathBuf::new(),
            cache_file: PathBuf::new(),
            config_file: PathBuf::new(),
            lock_file: PathBuf::new(),
            queue_file: PathBuf::new(),
            search_file: PathBuf::new(),
            cmdline_file: PathBuf::new(),

            silent: false,
            using_nonstandard_configs: false,
        };

        let env_home = dirs::home_dir();
        if env_home.is_none() {
            let uid = unsafe { libc::getuid() };

            config_paths.error_message = fmt!(
                &gettext(
                    "Fatal error: couldn't determine home directory!\n\
                     Please set the HOME environment variable or add \
                     a valid user for UID %u!"
                ),
                uid
            );

            return config_paths;
        }

        // hitting this branch means we found a home directory
        // we can now safely call unwrap on all other functions in the dirs crate

        config_paths.env_home = env_home.unwrap();
        config_paths.find_dirs();
        config_paths
    }

    fn migrate_data_from_newsbeuter_xdg(&mut self) -> bool {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_dirs = xdg::BaseDirectories::new().unwrap();
        let xdg_config_dir = xdg_dirs.get_config_home();
        let xdg_data_dir = xdg_dirs.get_data_home();

        let newsbeuter_config_dir = xdg_config_dir.join(NEWSBEUTER_SUBDIR_XDG);
        let newsbeuter_data_dir = xdg_data_dir.join(NEWSBEUTER_SUBDIR_XDG);

        let newsboat_config_dir = xdg_config_dir.join(NEWSBOAT_SUBDIR_XDG);
        let newsboat_data_dir = xdg_data_dir.join(NEWSBOAT_SUBDIR_XDG);

        if !newsbeuter_config_dir.is_dir() {
            return false;
        }

        fn exists(path: &Path) -> bool {
            let exists = path.exists();
            if exists {
                log!(
                    Level::Debug,
                    "{:?} already exists, aborting XDG migration.",
                    path
                );
            }
            return exists;
        }

        if exists(&newsboat_config_dir) {
            return false;
        }

        if exists(&newsboat_data_dir) {
            return false;
        }

        self.config_dir = newsboat_config_dir;
        self.data_dir = newsboat_data_dir;

        if !self.silent {
            eprintln!(
                "{}",
                &gettext("Migrating configs and data from Newsbeuter's XDG dirs...")
            );
        }

        if !try_mkdir(&self.config_dir) {
            return false;
        }

        if !try_mkdir(&self.data_dir) {
            return false;
        }

        // We ignore the return codes because it's okay if some files are missing.

        // in config
        let _ = migrate_file(&newsbeuter_config_dir, &self.config_dir, URLS_FILENAME);
        let _ = migrate_file(&newsbeuter_config_dir, &self.config_dir, CONFIG_FILENAME);

        // in data
        let _ = migrate_file(&newsbeuter_data_dir, &self.data_dir, CACHE_FILENAME);
        let _ = migrate_file(&newsbeuter_data_dir, &self.data_dir, QUEUE_FILENAME);
        let _ = migrate_file(
            &newsbeuter_data_dir,
            &self.data_dir,
            SEARCH_HISTORY_FILENAME,
        );
        let _ = migrate_file(
            &newsbeuter_data_dir,
            &self.data_dir,
            CMDLINE_HISTORY_FILENAME,
        );

        return true;
    }

    fn migrate_data_from_newsbeuter_simple(&self) -> bool {
        let newsbeuter_dir = self.env_home.join(NEWSBEUTER_CONFIG_SUBDIR);

        if !newsbeuter_dir.is_dir() {
            return false;
        }

        let newsboat_dir = self.env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        if newsboat_dir.exists() {
            log!(
                Level::Debug,
                "{:?} already exists, aborting migration.",
                newsboat_dir
            );
            return false;
        }

        if !self.silent {
            eprintln!(
                "{}",
                &gettext("Migrating configs and data from Newsbeuter's dotdir...")
            );
        }

        match mkdir(&newsboat_dir, 0o700) {
            Ok(_) => (),
            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
            Err(err) => {
                if !self.silent {
                    eprintln!(
                        "{}",
                        &fmt!(
                            &gettext("Aborting migration because mkdir on `%s' failed: %s"),
                            &newsboat_dir.to_string_lossy().into_owned(),
                            err.to_string()
                        )
                    );
                }
                return false;
            }
        };

        // We ignore the return codes because it's okay if some files are missing.
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, URLS_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, CACHE_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, CONFIG_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, QUEUE_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, SEARCH_HISTORY_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, CMDLINE_HISTORY_FILENAME);

        return true;
    }

    fn migrate_data_from_newsbeuter(&mut self) -> bool {
        let mut migrated = self.migrate_data_from_newsbeuter_xdg();

        if migrated {
            // Re-running to pick up XDG dirs
            self.find_dirs();
        } else {
            migrated = self.migrate_data_from_newsbeuter_simple();
        }

        migrated
    }

    pub fn create_dirs(&self) -> bool {
        return try_mkdir(&self.config_dir) && try_mkdir(&self.data_dir);
    }

    fn find_dirs(&mut self) {
        self.config_dir = self.env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        self.data_dir = self.config_dir.clone();

        // Will change config_dir and data_dir to point to XDG if XDG
        // directories are available.
        self.find_dirs_xdg();

        // in config
        self.url_file = self.config_dir.join(URLS_FILENAME);
        self.config_file = self.config_dir.join(CONFIG_FILENAME);

        // in data
        self.cache_file = self.data_dir.join(CACHE_FILENAME);
        self.lock_file = self.data_dir.join(CACHE_FILENAME.to_owned() + LOCK_SUFFIX);
        self.queue_file = self.data_dir.join(QUEUE_FILENAME);
        self.search_file = self.data_dir.join(SEARCH_HISTORY_FILENAME);
        self.cmdline_file = self.data_dir.join(CMDLINE_HISTORY_FILENAME);
    }

    fn find_dirs_xdg(&mut self) {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_dirs = xdg::BaseDirectories::new().unwrap();
        let config_dir = xdg_dirs.get_config_home().join(NEWSBOAT_SUBDIR_XDG);
        let data_dir = xdg_dirs.get_data_home().join(NEWSBOAT_SUBDIR_XDG);

        if !config_dir.is_dir() {
            return;
        }

        /* Invariant: config dir exists.
         *
         * At this point, we're confident we'll be using XDG. We don't check if
         * data dir exists, because if it doesn't we'll create it. */

        self.config_dir = config_dir;
        self.data_dir = data_dir;
    }

    /// Indicates if the object can be used.
    ///
    /// If this method returned `false`, the cause for initialization failure can be found using
    /// `error_message()`.
    pub fn initialized(&self) -> bool {
        !self.env_home.as_os_str().is_empty()
    }

    /// Returns explanation why initialization failed.
    ///
    /// \note You shouldn't call this unless `initialized()` returns `false`.
    pub fn error_message(&self) -> &str {
        &self.error_message
    }

    /// Initializes paths to config, cache etc. from CLI arguments.
    pub fn process_args(&mut self, args: &CliArgsParser) {
        if let Some(ref url_file) = args.url_file {
            self.url_file = url_file.to_owned();
        }

        if let Some(ref cache_file) = args.cache_file {
            self.cache_file = cache_file.to_owned();
        }

        if let Some(ref lock_file) = args.lock_file {
            self.lock_file = lock_file.to_owned();
        }

        if let Some(ref config_file) = args.config_file {
            self.config_file = config_file.to_owned();
        }

        self.silent = args.silent;
        self.using_nonstandard_configs = args.using_nonstandard_configs();
    }

    /// Migrate configs and data from Newsbeuter if they exist. Return `true` if migrated
    /// something, `false` otherwise.
    pub fn try_migrate_from_newsbeuter(&mut self) -> bool {
        if !self.using_nonstandard_configs && !&self.url_file.exists() {
            return self.migrate_data_from_newsbeuter();
        }

        // No migration occurred.
        false
    }

    /// Path to the URLs file.
    pub fn url_file(&self) -> &Path {
        &self.url_file
    }

    /// Path to the cache file.
    pub fn cache_file(&self) -> &Path {
        &self.cache_file
    }

    /// Sets path to the cache file.
    // FIXME: this is actually a kludge that lets Controller change the path
    // midway. That logic should be moved into ConfigPaths, and this method
    // removed.
    pub fn set_cache_file(&mut self, mut path: PathBuf) {
        self.cache_file = path.clone();
        self.lock_file = {
            let current_extension = path
                .extension()
                .and_then(|p| Some(p.to_string_lossy().into_owned()))
                .unwrap_or_else(|| String::new());
            path.set_extension(current_extension + LOCK_SUFFIX);
            path
        };
    }

    /// Path to the config file.
    pub fn config_file(&self) -> &Path {
        &self.config_file
    }

    /// Path to the lock file.
    ///
    /// \note This changes when path to config file changes.
    pub fn lock_file(&self) -> &Path {
        &self.lock_file
    }

    /// \brief Path to the queue file.
    ///
    /// Queue file stores enqueued podcasts. It's written by Newsboat, and read by Podboat.
    pub fn queue_file(&self) -> &Path {
        &self.queue_file
    }

    /// Path to the file with previous search queries.
    pub fn search_file(&self) -> &Path {
        &self.search_file
    }

    /// Path to the file with command-line history.
    pub fn cmdline_file(&self) -> &Path {
        &self.cmdline_file
    }
}

fn try_mkdir<R: AsRef<Path>>(path: R) -> bool {
    utils::mkdir_parents(&path.as_ref(), 0o700).is_ok()
}

fn mkdir<R: AsRef<Path>>(path: R, mode: u32) -> io::Result<()> {
    DirBuilder::new().mode(mode).create(path.as_ref())
}

fn migrate_file<R: AsRef<Path>>(newsbeuter_dir: R, newsboat_dir: R, file: &str) -> io::Result<()> {
    let input_filepath = newsbeuter_dir.as_ref().join(file);
    let output_filepath = newsboat_dir.as_ref().join(file);
    eprintln!("{:?} -> {:?}", input_filepath, output_filepath);
    fs::copy(input_filepath, output_filepath).map(|_| ())
}
<commit_msg>Make ConfigPaths message more translatable<commit_after>use cliargsparser::CliArgsParser;
use dirs;
use gettextrs::gettext;
use libc;
use logger::{self, Level};
use std::fs::{self, DirBuilder};
use std::io;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};
use utils;
use xdg;

pub const NEWSBOAT_SUBDIR_XDG: &str = "newsboat";
pub const NEWSBOAT_CONFIG_SUBDIR: &str = ".newsboat";
pub const NEWSBEUTER_SUBDIR_XDG: &str = "newsbeuter";
pub const NEWSBEUTER_CONFIG_SUBDIR: &str = ".newsbeuter";
pub const LOCK_SUFFIX: &str = ".lock";

const URLS_FILENAME: &str = "urls";
const CACHE_FILENAME: &str = "cache.db";
const CONFIG_FILENAME: &str = "config";
const QUEUE_FILENAME: &str = "queue";
const SEARCH_HISTORY_FILENAME: &str = "history.search";
const CMDLINE_HISTORY_FILENAME: &str = "history.cmdline";

#[derive(Debug)]
pub struct ConfigPaths {
    env_home: PathBuf,
    error_message: String,

    data_dir: PathBuf,
    config_dir: PathBuf,

    url_file: PathBuf,
    cache_file: PathBuf,
    config_file: PathBuf,
    lock_file: PathBuf,
    queue_file: PathBuf,
    search_file: PathBuf,
    cmdline_file: PathBuf,

    silent: bool,
    using_nonstandard_configs: bool,
}

impl ConfigPaths {
    pub fn new() -> ConfigPaths {
        let mut config_paths = ConfigPaths {
            env_home: PathBuf::new(),
            error_message: String::new(),

            data_dir: PathBuf::new(),
            config_dir: PathBuf::new(),

            url_file: PathBuf::new(),
            cache_file: PathBuf::new(),
            config_file: PathBuf::new(),
            lock_file: PathBuf::new(),
            queue_file: PathBuf::new(),
            search_file: PathBuf::new(),
            cmdline_file: PathBuf::new(),

            silent: false,
            using_nonstandard_configs: false,
        };

        let env_home = dirs::home_dir();
        if env_home.is_none() {
            let uid = unsafe { libc::getuid() };

            config_paths.error_message = fmt!(
                &gettext(
                    "Fatal error: couldn't determine home directory!\n\
                     Please set the HOME environment variable or add \
                     a valid user for UID %u!"
                ),
                uid
            );

            return config_paths;
        }

        // hitting this branch means we found a home directory
        // we can now safely call unwrap on all other functions in the dirs crate

        config_paths.env_home = env_home.unwrap();
        config_paths.find_dirs();
        config_paths
    }

    fn migrate_data_from_newsbeuter_xdg(&mut self) -> bool {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_dirs = xdg::BaseDirectories::new().unwrap();
        let xdg_config_dir = xdg_dirs.get_config_home();
        let xdg_data_dir = xdg_dirs.get_data_home();

        let newsbeuter_config_dir = xdg_config_dir.join(NEWSBEUTER_SUBDIR_XDG);
        let newsbeuter_data_dir = xdg_data_dir.join(NEWSBEUTER_SUBDIR_XDG);

        let newsboat_config_dir = xdg_config_dir.join(NEWSBOAT_SUBDIR_XDG);
        let newsboat_data_dir = xdg_data_dir.join(NEWSBOAT_SUBDIR_XDG);

        if !newsbeuter_config_dir.is_dir() {
            return false;
        }

        fn exists(path: &Path) -> bool {
            let exists = path.exists();
            if exists {
                log!(
                    Level::Debug,
                    "{:?} already exists, aborting XDG migration.",
                    path
                );
            }
            return exists;
        }

        if exists(&newsboat_config_dir) {
            return false;
        }

        if exists(&newsboat_data_dir) {
            return false;
        }

        self.config_dir = newsboat_config_dir;
        self.data_dir = newsboat_data_dir;

        if !self.silent {
            eprintln!(
                "{}",
                &gettext("Migrating configs and data from Newsbeuter's XDG dirs...")
            );
        }

        if !try_mkdir(&self.config_dir) {
            return false;
        }

        if !try_mkdir(&self.data_dir) {
            return false;
        }

        // We ignore the return codes because it's okay if some files are missing.

        // in config
        let _ = migrate_file(&newsbeuter_config_dir, &self.config_dir, URLS_FILENAME);
        let _ = migrate_file(&newsbeuter_config_dir, &self.config_dir, CONFIG_FILENAME);

        // in data
        let _ = migrate_file(&newsbeuter_data_dir, &self.data_dir, CACHE_FILENAME);
        let _ = migrate_file(&newsbeuter_data_dir, &self.data_dir, QUEUE_FILENAME);
        let _ = migrate_file(
            &newsbeuter_data_dir,
            &self.data_dir,
            SEARCH_HISTORY_FILENAME,
        );
        let _ = migrate_file(
            &newsbeuter_data_dir,
            &self.data_dir,
            CMDLINE_HISTORY_FILENAME,
        );

        return true;
    }

    fn migrate_data_from_newsbeuter_simple(&self) -> bool {
        let newsbeuter_dir = self.env_home.join(NEWSBEUTER_CONFIG_SUBDIR);

        if !newsbeuter_dir.is_dir() {
            return false;
        }

        let newsboat_dir = self.env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        if newsboat_dir.exists() {
            log!(
                Level::Debug,
                "{:?} already exists, aborting migration.",
                newsboat_dir
            );
            return false;
        }

        if !self.silent {
            eprintln!(
                "{}",
                &gettext("Migrating configs and data from ~/.newsbeuter/...")
            );
        }

        match mkdir(&newsboat_dir, 0o700) {
            Ok(_) => (),
            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
            Err(err) => {
                if !self.silent {
                    eprintln!(
                        "{}",
                        &fmt!(
                            &gettext("Aborting migration because mkdir on `%s' failed: %s"),
                            &newsboat_dir.to_string_lossy().into_owned(),
                            err.to_string()
                        )
                    );
                }
                return false;
            }
        };

        // We ignore the return codes because it's okay if some files are missing.
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, URLS_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, CACHE_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, CONFIG_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, QUEUE_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, SEARCH_HISTORY_FILENAME);
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, CMDLINE_HISTORY_FILENAME);

        return true;
    }

    fn migrate_data_from_newsbeuter(&mut self) -> bool {
        let mut migrated = self.migrate_data_from_newsbeuter_xdg();

        if migrated {
            // Re-running to pick up XDG dirs
            self.find_dirs();
        } else {
            migrated = self.migrate_data_from_newsbeuter_simple();
        }

        migrated
    }

    pub fn create_dirs(&self) -> bool {
        return try_mkdir(&self.config_dir) && try_mkdir(&self.data_dir);
    }

    fn find_dirs(&mut self) {
        self.config_dir = self.env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        self.data_dir = self.config_dir.clone();

        // Will change config_dir and data_dir to point to XDG if XDG
        // directories are available.
        self.find_dirs_xdg();

        // in config
        self.url_file = self.config_dir.join(URLS_FILENAME);
        self.config_file = self.config_dir.join(CONFIG_FILENAME);

        // in data
        self.cache_file = self.data_dir.join(CACHE_FILENAME);
        self.lock_file = self.data_dir.join(CACHE_FILENAME.to_owned() + LOCK_SUFFIX);
        self.queue_file = self.data_dir.join(QUEUE_FILENAME);
        self.search_file = self.data_dir.join(SEARCH_HISTORY_FILENAME);
        self.cmdline_file = self.data_dir.join(CMDLINE_HISTORY_FILENAME);
    }

    fn find_dirs_xdg(&mut self) {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_dirs = xdg::BaseDirectories::new().unwrap();
        let config_dir = xdg_dirs.get_config_home().join(NEWSBOAT_SUBDIR_XDG);
        let data_dir = xdg_dirs.get_data_home().join(NEWSBOAT_SUBDIR_XDG);

        if !config_dir.is_dir() {
            return;
        }

        /* Invariant: config dir exists.
         *
         * At this point, we're confident we'll be using XDG. We don't check if
         * data dir exists, because if it doesn't we'll create it. */

        self.config_dir = config_dir;
        self.data_dir = data_dir;
    }

    /// Indicates if the object can be used.
    ///
    /// If this method returned `false`, the cause for initialization failure can be found using
    /// `error_message()`.
    pub fn initialized(&self) -> bool {
        !self.env_home.as_os_str().is_empty()
    }

    /// Returns explanation why initialization failed.
    ///
    /// \note You shouldn't call this unless `initialized()` returns `false`.
    pub fn error_message(&self) -> &str {
        &self.error_message
    }

    /// Initializes paths to config, cache etc. from CLI arguments.
    pub fn process_args(&mut self, args: &CliArgsParser) {
        if let Some(ref url_file) = args.url_file {
            self.url_file = url_file.to_owned();
        }

        if let Some(ref cache_file) = args.cache_file {
            self.cache_file = cache_file.to_owned();
        }

        if let Some(ref lock_file) = args.lock_file {
            self.lock_file = lock_file.to_owned();
        }

        if let Some(ref config_file) = args.config_file {
            self.config_file = config_file.to_owned();
        }

        self.silent = args.silent;
        self.using_nonstandard_configs = args.using_nonstandard_configs();
    }

    /// Migrate configs and data from Newsbeuter if they exist. Return `true` if migrated
    /// something, `false` otherwise.
    pub fn try_migrate_from_newsbeuter(&mut self) -> bool {
        if !self.using_nonstandard_configs && !&self.url_file.exists() {
            return self.migrate_data_from_newsbeuter();
        }

        // No migration occurred.
        false
    }

    /// Path to the URLs file.
    pub fn url_file(&self) -> &Path {
        &self.url_file
    }

    /// Path to the cache file.
    pub fn cache_file(&self) -> &Path {
        &self.cache_file
    }

    /// Sets path to the cache file.
    // FIXME: this is actually a kludge that lets Controller change the path
    // midway. That logic should be moved into ConfigPaths, and this method
    // removed.
    pub fn set_cache_file(&mut self, mut path: PathBuf) {
        self.cache_file = path.clone();
        self.lock_file = {
            let current_extension = path
                .extension()
                .and_then(|p| Some(p.to_string_lossy().into_owned()))
                .unwrap_or_else(|| String::new());
            path.set_extension(current_extension + LOCK_SUFFIX);
            path
        };
    }

    /// Path to the config file.
    pub fn config_file(&self) -> &Path {
        &self.config_file
    }

    /// Path to the lock file.
    ///
    /// \note This changes when path to config file changes.
    pub fn lock_file(&self) -> &Path {
        &self.lock_file
    }

    /// \brief Path to the queue file.
    ///
    /// Queue file stores enqueued podcasts. It's written by Newsboat, and read by Podboat.
    pub fn queue_file(&self) -> &Path {
        &self.queue_file
    }

    /// Path to the file with previous search queries.
    pub fn search_file(&self) -> &Path {
        &self.search_file
    }

    /// Path to the file with command-line history.
    pub fn cmdline_file(&self) -> &Path {
        &self.cmdline_file
    }
}

fn try_mkdir<R: AsRef<Path>>(path: R) -> bool {
    utils::mkdir_parents(&path.as_ref(), 0o700).is_ok()
}

fn mkdir<R: AsRef<Path>>(path: R, mode: u32) -> io::Result<()> {
    DirBuilder::new().mode(mode).create(path.as_ref())
}

fn migrate_file<R: AsRef<Path>>(newsbeuter_dir: R, newsboat_dir: R, file: &str) -> io::Result<()> {
    let input_filepath = newsbeuter_dir.as_ref().join(file);
    let output_filepath = newsboat_dir.as_ref().join(file);
    eprintln!("{:?} -> {:?}", input_filepath, output_filepath);
    fs::copy(input_filepath, output_filepath).map(|_| ())
}
<|endoftext|>


<commit_before>use crate::{
    analysis::{
        conversion_type::ConversionType, function_parameters::CParameter,
        functions::is_carray_with_direct_elements, imports::Imports, ref_mode::RefMode,
        return_value, rust_type::parameter_rust_type,
    },
    config,
    env::Env,
    library::*,
    nameutil,
};
use log::error;
use std::slice::Iter;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Mode {
    None,
    Normal,
    Optional,
    Combined,
    //<use function return>
    Throws(bool),
}

impl Default for Mode {
    fn default() -> Mode {
        Mode::None
    }
}

#[derive(Debug, Default)]
pub struct Info {
    pub mode: Mode,
    pub params: Vec<Parameter>,
}

impl Info {
    pub fn is_empty(&self) -> bool {
        self.mode == Mode::None
    }

    pub fn iter(&self) -> Iter<'_, Parameter> {
        self.params.iter()
    }
}

pub fn analyze(
    env: &Env,
    func: &Function,
    func_c_params: &[CParameter],
    func_ret: &return_value::Info,
    configured_functions: &[&config::functions::Function],
) -> (Info, bool) {
    let mut info: Info = Default::default();
    let mut unsupported_outs = false;

    let nullable_override = configured_functions
        .iter()
        .filter_map(|f| f.ret.nullable)
        .next();
    if func.throws {
        let use_ret = use_return_value_for_result(env, func_ret, &func.name, configured_functions);
        info.mode = Mode::Throws(use_ret);
    } else if func.ret.typ == TypeId::tid_none() {
        info.mode = Mode::Normal;
    } else if func.ret.typ == TypeId::tid_bool() {
        if nullable_override == Some(Nullable(false)) {
            info.mode = Mode::Combined;
        } else {
            info.mode = Mode::Optional;
        }
    } else {
        info.mode = Mode::Combined;
    }

    for par in &func.parameters {
        if par.direction != ParameterDirection::Out {
            continue;
        }
        if can_as_return(env, par) {
            let mut par = par.clone();
            par.name = nameutil::mangle_keywords(&*par.name).into_owned();
            // FIXME: temporary solution for string_type, nullable override. This should completely
            // work based on the analyzed parameters instead of the library parameters.
            if let Some(c_par) = func_c_params.iter().find(|c_par| c_par.name == par.name) {
                par.typ = c_par.typ;
                par.nullable = c_par.nullable;
            }
            info.params.push(par);
        } else {
            unsupported_outs = true;
        }
    }

    if info.params.is_empty() {
        info.mode = Mode::None;
    }
    if info.mode == Mode::Combined || info.mode == Mode::Throws(true) {
        let mut ret = func.ret.clone();
        //TODO: fully switch to use analyzed returns (it add too many Return<Option<>>)
        if let Some(ref par) = func_ret.parameter {
            ret.typ = par.typ;
        }
        if let Some(val) = nullable_override {
            ret.nullable = val;
        }
        info.params.insert(0, ret);
    }

    (info, unsupported_outs)
}

pub fn analyze_imports(env: &Env, parameters: &[Parameter], imports: &mut Imports) {
    for par in parameters {
        if par.direction == ParameterDirection::Out {
            match *env.library.type_(par.typ) {
                Type::Bitfield(..) | Type::Enumeration(..) => imports.add("std::mem"),
                Type::Fundamental(fund)
                    if fund != Fundamental::Utf8
                        && fund != Fundamental::OsString
                        && fund != Fundamental::Filename =>
                {
                    imports.add("std::mem")
                }
                _ if !par.caller_allocates => match ConversionType::of(env, par.typ) {
                    ConversionType::Direct | ConversionType::Scalar => (),
                    _ => imports.add("std::ptr"),
                },
                _ => (),
            }
        }
    }
}

pub fn can_as_return(env: &Env, par: &Parameter) -> bool {
    use super::conversion_type::ConversionType::*;
    match ConversionType::of(env, par.typ) {
        Direct => true,
        Scalar => true,
        Pointer => {
            // Disallow fundamental arrays without length
            if is_carray_with_direct_elements(env, par.typ) && par.array_length.is_none() {
                return false;
            }

            parameter_rust_type(
                env,
                par.typ,
                ParameterDirection::Out,
                Nullable(false),
                RefMode::None,
                par.scope,
            )
            .is_ok()
        }
        Borrow => false,
        Unknown => false,
    }
}

pub fn use_return_value_for_result(
    env: &Env,
    ret: &return_value::Info,
    func_name: &str,
    configured_functions: &[&config::functions::Function],
) -> bool {
    let typ = ret.parameter.as_ref().map(|x| x.typ).unwrap_or_default();
    use_function_return_for_result(env, typ, func_name, configured_functions)
}

pub fn use_function_return_for_result(
    env: &Env,
    typ: TypeId,
    func_name: &str,
    configured_functions: &[&config::functions::Function],
) -> bool {
    // Configuration takes precendence over everything.
    let use_return_for_result = configured_functions
        .iter()
        .filter_map(|f| f.ret.use_return_for_result.as_ref())
        .next();
    if let Some(use_return_for_result) = use_return_for_result {
        if typ == Default::default() {
            error!("Function \"{}\": use_return_for_result set to true, but function has no return value", func_name);
            return false;
        }
        return *use_return_for_result;
    }

    if typ == Default::default() {
        return false;
    }
    if typ.ns_id != INTERNAL_NAMESPACE {
        return true;
    }
    let type_ = env.type_(typ);
    !matches!(&*type_.get_name(), "UInt" | "Boolean")
}
<commit_msg>out_param: follow aliases for imports<commit_after>use crate::{
    analysis::{
        conversion_type::ConversionType, function_parameters::CParameter,
        functions::is_carray_with_direct_elements, imports::Imports, ref_mode::RefMode,
        return_value, rust_type::parameter_rust_type,
    },
    config,
    env::Env,
    library::*,
    nameutil,
};
use log::error;
use std::slice::Iter;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Mode {
    None,
    Normal,
    Optional,
    Combined,
    //<use function return>
    Throws(bool),
}

impl Default for Mode {
    fn default() -> Mode {
        Mode::None
    }
}

#[derive(Debug, Default)]
pub struct Info {
    pub mode: Mode,
    pub params: Vec<Parameter>,
}

impl Info {
    pub fn is_empty(&self) -> bool {
        self.mode == Mode::None
    }

    pub fn iter(&self) -> Iter<'_, Parameter> {
        self.params.iter()
    }
}

pub fn analyze(
    env: &Env,
    func: &Function,
    func_c_params: &[CParameter],
    func_ret: &return_value::Info,
    configured_functions: &[&config::functions::Function],
) -> (Info, bool) {
    let mut info: Info = Default::default();
    let mut unsupported_outs = false;

    let nullable_override = configured_functions
        .iter()
        .filter_map(|f| f.ret.nullable)
        .next();
    if func.throws {
        let use_ret = use_return_value_for_result(env, func_ret, &func.name, configured_functions);
        info.mode = Mode::Throws(use_ret);
    } else if func.ret.typ == TypeId::tid_none() {
        info.mode = Mode::Normal;
    } else if func.ret.typ == TypeId::tid_bool() {
        if nullable_override == Some(Nullable(false)) {
            info.mode = Mode::Combined;
        } else {
            info.mode = Mode::Optional;
        }
    } else {
        info.mode = Mode::Combined;
    }

    for par in &func.parameters {
        if par.direction != ParameterDirection::Out {
            continue;
        }
        if can_as_return(env, par) {
            let mut par = par.clone();
            par.name = nameutil::mangle_keywords(&*par.name).into_owned();
            // FIXME: temporary solution for string_type, nullable override. This should completely
            // work based on the analyzed parameters instead of the library parameters.
            if let Some(c_par) = func_c_params.iter().find(|c_par| c_par.name == par.name) {
                par.typ = c_par.typ;
                par.nullable = c_par.nullable;
            }
            info.params.push(par);
        } else {
            unsupported_outs = true;
        }
    }

    if info.params.is_empty() {
        info.mode = Mode::None;
    }
    if info.mode == Mode::Combined || info.mode == Mode::Throws(true) {
        let mut ret = func.ret.clone();
        //TODO: fully switch to use analyzed returns (it add too many Return<Option<>>)
        if let Some(ref par) = func_ret.parameter {
            ret.typ = par.typ;
        }
        if let Some(val) = nullable_override {
            ret.nullable = val;
        }
        info.params.insert(0, ret);
    }

    (info, unsupported_outs)
}

pub fn analyze_imports(env: &Env, parameters: &[Parameter], imports: &mut Imports) {
    for par in parameters {
        if par.direction == ParameterDirection::Out {
            analyze_type_imports(env, par.typ, par.caller_allocates, imports);
        }
    }
}

fn analyze_type_imports(env: &Env, typ: TypeId, caller_allocates: bool, imports: &mut Imports) {
    match *env.library.type_(typ) {
        Type::Alias(ref alias) => analyze_type_imports(env, alias.typ, caller_allocates, imports),
        Type::Bitfield(..) | Type::Enumeration(..) => imports.add("std::mem"),
        Type::Fundamental(fund)
            if fund != Fundamental::Utf8
                && fund != Fundamental::OsString
                && fund != Fundamental::Filename =>
        {
            imports.add("std::mem")
        }
        _ if !caller_allocates => match ConversionType::of(env, typ) {
            ConversionType::Direct | ConversionType::Scalar => (),
            _ => imports.add("std::ptr"),
        },
        _ => (),
    }
}

pub fn can_as_return(env: &Env, par: &Parameter) -> bool {
    use super::conversion_type::ConversionType::*;
    match ConversionType::of(env, par.typ) {
        Direct => true,
        Scalar => true,
        Pointer => {
            // Disallow fundamental arrays without length
            if is_carray_with_direct_elements(env, par.typ) && par.array_length.is_none() {
                return false;
            }

            parameter_rust_type(
                env,
                par.typ,
                ParameterDirection::Out,
                Nullable(false),
                RefMode::None,
                par.scope,
            )
            .is_ok()
        }
        Borrow => false,
        Unknown => false,
    }
}

pub fn use_return_value_for_result(
    env: &Env,
    ret: &return_value::Info,
    func_name: &str,
    configured_functions: &[&config::functions::Function],
) -> bool {
    let typ = ret.parameter.as_ref().map(|x| x.typ).unwrap_or_default();
    use_function_return_for_result(env, typ, func_name, configured_functions)
}

pub fn use_function_return_for_result(
    env: &Env,
    typ: TypeId,
    func_name: &str,
    configured_functions: &[&config::functions::Function],
) -> bool {
    // Configuration takes precendence over everything.
    let use_return_for_result = configured_functions
        .iter()
        .filter_map(|f| f.ret.use_return_for_result.as_ref())
        .next();
    if let Some(use_return_for_result) = use_return_for_result {
        if typ == Default::default() {
            error!("Function \"{}\": use_return_for_result set to true, but function has no return value", func_name);
            return false;
        }
        return *use_return_for_result;
    }

    if typ == Default::default() {
        return false;
    }
    if typ.ns_id != INTERNAL_NAMESPACE {
        return true;
    }
    let type_ = env.type_(typ);
    !matches!(&*type_.get_name(), "UInt" | "Boolean")
}
<|endoftext|>


<commit_before>//! Lenny Face Generator (づσ.σ)づ .
//!
//! This module maintains a set of hardcoded components for Lenny Faces. Generate as many of them
//! as you need.
//!
//! Source: <https://github.com/Twentysix26/Red-Cogs>

use rand::random;

static EARS: &'static [(&'static str, &'static str)] = include!("lenny_ears.inc");
static EYES: &'static [(&'static str, &'static str)] = include!("lenny_eyes.inc");
static MOUTHS: &'static [&'static str] = include!("lenny_mouths.inc");

/// Generate a new Lenny Face.
pub fn new() -> String {
    let (ref l_ear, ref r_ear) = EARS[random::<usize>() % EARS.len()];
    let (ref l_eye, ref r_eye) = EYES[random::<usize>() % EYES.len()];
    let ref mouth = MOUTHS[random::<usize>() % MOUTHS.len()];
    format!("{}{}{}{}{}", l_ear, l_eye, mouth, r_eye, r_ear)
}

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

    #[test]
    fn generate() {
        println!("{}", new());
    }
}
<commit_msg>Utils: Add lenny::fliptable<commit_after>//! Lenny Face Generator (づσ.σ)づ .
//!
//! This module maintains a set of hardcoded components for [Lenny Faces]. Generate as many of them
//! as you need.
//!
//! [Lenny Faces]: <http://knowyourmeme.com/memes/lenny-face>
//!
//! Source: <https://github.com/Twentysix26/Red-Cogs>

use rand::random;

static EARS: &'static [(&'static str, &'static str)] = include!("lenny_ears.inc");
static EYES: &'static [(&'static str, &'static str)] = include!("lenny_eyes.inc");
static MOUTHS: &'static [&'static str] = include!("lenny_mouths.inc");

/// Generate a new Lenny Face.
pub fn gen() -> String {
    let (ref l_ear, ref r_ear) = EARS[random::<usize>() % EARS.len()];
    let (ref l_eye, ref r_eye) = EYES[random::<usize>() % EYES.len()];
    let ref mouth = MOUTHS[random::<usize>() % MOUTHS.len()];
    format!("{}{}{}{}{}", l_ear, l_eye, mouth, r_eye, r_ear)
}

/// (╯°□°）╯︵ ┻━┻
pub fn fliptable() -> String {
    "(╯°□°）╯︵ ┻━┻".to_owned()
}

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

    #[test]
    fn generate() {
        println!("{}", gen());
    }
}
<|endoftext|>


<commit_before>// DOCS

#![allow(unstable)]
#![feature(plugin)]
#[plugin] #[no_link] extern crate peg_syntax_ext;

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};

pub struct Property {
    /// Parameters.
    pub params: HashMap<String, String>,

    /// Value as unparsed string.
    pub raw_value: String,

    /// Property group. E.g. a contentline like `foo.FN:Markus` would result in the group being
    /// `"foo"`.
    pub prop_group: Option<String>
}

impl Property {
    /// Create property from unescaped string.
    pub fn new(value: &str) -> Property {
        Property {
            params: HashMap::new(),
            raw_value: escape_chars(value),
            prop_group: None
        }
    }

    /// Get value as unescaped string.
    pub fn value_as_string(&self) -> String {
        unescape_chars(self.raw_value.as_slice())
    }
}

pub struct Component {
    /// The name of the component, such as `VCARD` or `VEVENT`.
    pub name: String,

    /// The component's properties.
    pub props: HashMap<String, Vec<Property>>,

    /// The component's child- or sub-components.
    pub subcomponents: Vec<Component>
}

impl Component {
    pub fn new(name: &str) -> Component {
        Component {
            name: name.to_string(),
            props: HashMap::new(),
            subcomponents: vec![]
        }
    }

    /// Retrieve one property (from many) by key. Returns `None` if nothing is found.
    pub fn single_prop(&self, key: &str) -> Option<&Property> {
        match self.props.get(key) {
            Some(x) => {
                match x.len() {
                    1 => Some(&x[0]),
                    _ => None
                }
            },
            None => None
        }
    }

    /// Retrieve a mutable vector of properties for this key. Creates one (and inserts it into the
    /// component) if none exists.
    pub fn all_props_mut(&mut self, key: &str) -> &mut Vec<Property> {
        match self.props.entry(String::from_str(key)) {
            Occupied(values) => values.into_mut(),
            Vacant(values) => values.insert(vec![])
        }
    }

    /// Retrieve properties by key. Returns an empty slice if key doesn't exist.
    pub fn all_props(&self, key: &str) -> &[Property] {
        static EMPTY: &'static [Property] = &[];
        match self.props.get(key) {
            Some(values) => values.as_slice(),
            None => EMPTY
        }
    }
}

/// Parse a component. The error value is a human-readable message.
pub fn parse_component(s: &str) -> Result<Component, String> {
    // XXX: The unfolding should be worked into the PEG
    // See feature request: https://github.com/kevinmehall/rust-peg/issues/26
    let unfolded = unfold_lines(s);
    parser::component(unfolded.as_slice())
}

/// Write a component. The error value is a human-readable message.
pub fn write_component(c: &Component) -> String {
    fn inner(buf: &mut String, c: &Component) {
        buf.push_str("BEGIN:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");

        for (prop_name, props) in c.props.iter() {
            for prop in props.iter() {
                match prop.prop_group {
                    Some(ref x) => { buf.push_str(x.as_slice()); buf.push('.'); },
                    None => ()
                };
                buf.push_str(prop_name.as_slice());
                for (param_key, param_value) in prop.params.iter() {
                    buf.push(';');
                    buf.push_str(param_key.as_slice());
                    buf.push('=');
                    buf.push_str(param_value.as_slice());
                };
                buf.push(':');
                buf.push_str(fold_line(prop.raw_value.as_slice()).as_slice());
                buf.push_str("\r\n");
            };
        };

        for subcomponent in c.subcomponents.iter() {
            inner(buf, subcomponent);
        };

        buf.push_str("END:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");
    }

    let mut buf = String::new();
    inner(&mut buf, c);
    buf
}

/// Escape text for a VObject property value.
pub fn escape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\n")
        .replace("\\", "\\\\")
        .replace(";", "\\;")
        .replace(",", "\\,")
        .replace("\r\n", "\\n")
        .replace("\n", "\\n")
}

/// Unescape text from a VObject property value.
pub fn unescape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\\n")
        .replace("\r\n", "\n")
        .replace("\\n", "\n")
        .replace("\\,", ",")
        .replace("\\;", ";")
        .replace("\\\\", "\\")
}

/// Unfold contentline.
pub fn unfold_lines(s: &str) -> String {
    s
        .replace("\r\n ", "").replace("\r\n\t", "")
        .replace("\n ", "").replace("\n\t", "")
        .replace("\r ", "").replace("\r\t", "")
}

/// Fold contentline to 75 chars. This function assumes the input to be unfolded, which means no
/// '\n' or '\r' in it.
pub fn fold_line(s: &str) -> String {
    let mut rv = String::new();
    for (i, c) in s.chars().enumerate() {
        rv.push(c);
        if i != 0 && i % 75 == 0 {
            rv.push_str("\r\n ");
        };
    };
    rv
}

peg! parser(r#"
use super::{Component,Property};
use std::collections::HashMap;

components -> Vec<Component>
    = cs:component ** eols __ { cs }

    #[pub]
    component -> Component
        = name:component_begin
          ps:props
          cs:components
          component_end {
            let mut rv = Component::new(name);
            rv.subcomponents = cs;

            for (k, v) in ps.into_iter() {
                rv.all_props_mut(k).push(v);
            };

            rv
        }

    component_begin -> &'input str
        = "BEGIN:" v:value __ { v }

    component_end -> &'input str
        = "END:" v:value __ { v }

props -> Vec<(&'input str, Property)>
    = ps:prop ++ eols __ { ps }

    prop -> (&'input str, Property)
        = !"BEGIN:" !"END:" g:group? k:name p:params ":" v:value {
            (k, Property { params: p, raw_value: v.to_string(), prop_group: g })
        }

    group -> String
        = g:group_name "." { g.to_string() }

        group_name -> &'input str
            = group_char+ { match_str }

    name -> &'input str
        = iana_token+ { match_str }

    params -> HashMap<String, String>
        = ps:(";" p:param {p})* {
            let mut rv: HashMap<String, String> = HashMap::with_capacity(ps.len());
            rv.extend(ps.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
            rv
        }

        param -> (&'input str, &'input str)
            // FIXME: Doesn't handle comma-separated values
            = k:param_name v:("=" v:param_value { v })? {
                (k, match v {
                    Some(x) => x,
                    None => ""
                })
            }

        param_name -> &'input str
            = iana_token+ { match_str }

        param_value -> &'input str
            = x:(quoted_string / param_text) { x }

        param_text -> &'input str
            = safe_char* { match_str }

    value -> &'input str
        = value_char+ { match_str }

quoted_string -> &'input str
    = dquote x:quoted_content dquote { x }

quoted_content -> &'input str
    = qsafe_char* { match_str }

iana_token = ([a-zA-Z0-9] / "-")+
group_char = ([a-zA-Z0-9] / "-")
qsafe_char = !dquote !ctl value_char
safe_char = !";" !":" qsafe_char

value_char = !eol .

eol = "\r\n" / "\n" / "\r"
dquote = "\""
eols = eol+

// Taken from vCard. vCalendar's is a subset. Together with the definition of "qsafe_char" this
// might reject a bunch of valid iCalendars, but I can't imagine one.
ctl = [\u{00}-\u{1F}] / "\u{7F}"

whitespace = " " / "\t"
__ = (eol / whitespace)*

"#);
<commit_msg>implement FromStr<commit_after>// DOCS

#![allow(unstable)]
#![feature(plugin)]
#[plugin] #[no_link] extern crate peg_syntax_ext;

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::str::FromStr;

pub struct Property {
    /// Parameters.
    pub params: HashMap<String, String>,

    /// Value as unparsed string.
    pub raw_value: String,

    /// Property group. E.g. a contentline like `foo.FN:Markus` would result in the group being
    /// `"foo"`.
    pub prop_group: Option<String>
}

impl Property {
    /// Create property from unescaped string.
    pub fn new(value: &str) -> Property {
        Property {
            params: HashMap::new(),
            raw_value: escape_chars(value),
            prop_group: None
        }
    }

    /// Get value as unescaped string.
    pub fn value_as_string(&self) -> String {
        unescape_chars(self.raw_value.as_slice())
    }
}

pub struct Component {
    /// The name of the component, such as `VCARD` or `VEVENT`.
    pub name: String,

    /// The component's properties.
    pub props: HashMap<String, Vec<Property>>,

    /// The component's child- or sub-components.
    pub subcomponents: Vec<Component>
}

impl Component {
    pub fn new(name: &str) -> Component {
        Component {
            name: name.to_string(),
            props: HashMap::new(),
            subcomponents: vec![]
        }
    }

    /// Retrieve one property (from many) by key. Returns `None` if nothing is found.
    pub fn single_prop(&self, key: &str) -> Option<&Property> {
        match self.props.get(key) {
            Some(x) => {
                match x.len() {
                    1 => Some(&x[0]),
                    _ => None
                }
            },
            None => None
        }
    }

    /// Retrieve a mutable vector of properties for this key. Creates one (and inserts it into the
    /// component) if none exists.
    pub fn all_props_mut(&mut self, key: &str) -> &mut Vec<Property> {
        match self.props.entry(String::from_str(key)) {
            Occupied(values) => values.into_mut(),
            Vacant(values) => values.insert(vec![])
        }
    }

    /// Retrieve properties by key. Returns an empty slice if key doesn't exist.
    pub fn all_props(&self, key: &str) -> &[Property] {
        static EMPTY: &'static [Property] = &[];
        match self.props.get(key) {
            Some(values) => values.as_slice(),
            None => EMPTY
        }
    }
}

impl FromStr for Component {
    /// Same as `vobject::parse_component`, but without the error messages.
    fn from_str(s: &str) -> Option<Component> {
        match parse_component(s) {
            Ok(x) => Some(x),
            Err(_) => None
        }
    }
}

/// Parse a component. The error value is a human-readable message.
pub fn parse_component(s: &str) -> Result<Component, String> {
    // XXX: The unfolding should be worked into the PEG
    // See feature request: https://github.com/kevinmehall/rust-peg/issues/26
    let unfolded = unfold_lines(s);
    parser::component(unfolded.as_slice())
}

/// Write a component. The error value is a human-readable message.
pub fn write_component(c: &Component) -> String {
    fn inner(buf: &mut String, c: &Component) {
        buf.push_str("BEGIN:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");

        for (prop_name, props) in c.props.iter() {
            for prop in props.iter() {
                match prop.prop_group {
                    Some(ref x) => { buf.push_str(x.as_slice()); buf.push('.'); },
                    None => ()
                };
                buf.push_str(prop_name.as_slice());
                for (param_key, param_value) in prop.params.iter() {
                    buf.push(';');
                    buf.push_str(param_key.as_slice());
                    buf.push('=');
                    buf.push_str(param_value.as_slice());
                };
                buf.push(':');
                buf.push_str(fold_line(prop.raw_value.as_slice()).as_slice());
                buf.push_str("\r\n");
            };
        };

        for subcomponent in c.subcomponents.iter() {
            inner(buf, subcomponent);
        };

        buf.push_str("END:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");
    }

    let mut buf = String::new();
    inner(&mut buf, c);
    buf
}

/// Escape text for a VObject property value.
pub fn escape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\n")
        .replace("\\", "\\\\")
        .replace(";", "\\;")
        .replace(",", "\\,")
        .replace("\r\n", "\\n")
        .replace("\n", "\\n")
}

/// Unescape text from a VObject property value.
pub fn unescape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\\n")
        .replace("\r\n", "\n")
        .replace("\\n", "\n")
        .replace("\\,", ",")
        .replace("\\;", ";")
        .replace("\\\\", "\\")
}

/// Unfold contentline.
pub fn unfold_lines(s: &str) -> String {
    s
        .replace("\r\n ", "").replace("\r\n\t", "")
        .replace("\n ", "").replace("\n\t", "")
        .replace("\r ", "").replace("\r\t", "")
}

/// Fold contentline to 75 chars. This function assumes the input to be unfolded, which means no
/// '\n' or '\r' in it.
pub fn fold_line(s: &str) -> String {
    let mut rv = String::new();
    for (i, c) in s.chars().enumerate() {
        rv.push(c);
        if i != 0 && i % 75 == 0 {
            rv.push_str("\r\n ");
        };
    };
    rv
}

peg! parser(r#"
use super::{Component,Property};
use std::collections::HashMap;

components -> Vec<Component>
    = cs:component ** eols __ { cs }

    #[pub]
    component -> Component
        = name:component_begin
          ps:props
          cs:components
          component_end {
            let mut rv = Component::new(name);
            rv.subcomponents = cs;

            for (k, v) in ps.into_iter() {
                rv.all_props_mut(k).push(v);
            };

            rv
        }

    component_begin -> &'input str
        = "BEGIN:" v:value __ { v }

    component_end -> &'input str
        = "END:" v:value __ { v }

props -> Vec<(&'input str, Property)>
    = ps:prop ++ eols __ { ps }

    prop -> (&'input str, Property)
        = !"BEGIN:" !"END:" g:group? k:name p:params ":" v:value {
            (k, Property { params: p, raw_value: v.to_string(), prop_group: g })
        }

    group -> String
        = g:group_name "." { g.to_string() }

        group_name -> &'input str
            = group_char+ { match_str }

    name -> &'input str
        = iana_token+ { match_str }

    params -> HashMap<String, String>
        = ps:(";" p:param {p})* {
            let mut rv: HashMap<String, String> = HashMap::with_capacity(ps.len());
            rv.extend(ps.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
            rv
        }

        param -> (&'input str, &'input str)
            // FIXME: Doesn't handle comma-separated values
            = k:param_name v:("=" v:param_value { v })? {
                (k, match v {
                    Some(x) => x,
                    None => ""
                })
            }

        param_name -> &'input str
            = iana_token+ { match_str }

        param_value -> &'input str
            = x:(quoted_string / param_text) { x }

        param_text -> &'input str
            = safe_char* { match_str }

    value -> &'input str
        = value_char+ { match_str }

quoted_string -> &'input str
    = dquote x:quoted_content dquote { x }

quoted_content -> &'input str
    = qsafe_char* { match_str }

iana_token = ([a-zA-Z0-9] / "-")+
group_char = ([a-zA-Z0-9] / "-")
qsafe_char = !dquote !ctl value_char
safe_char = !";" !":" qsafe_char

value_char = !eol .

eol = "\r\n" / "\n" / "\r"
dquote = "\""
eols = eol+

// Taken from vCard. vCalendar's is a subset. Together with the definition of "qsafe_char" this
// might reject a bunch of valid iCalendars, but I can't imagine one.
ctl = [\u{00}-\u{1F}] / "\u{7F}"

whitespace = " " / "\t"
__ = (eol / whitespace)*

"#);
<|endoftext|>


<commit_before>//! Missing batteries for standard libraries.
use std::{cmp::Ordering, ops, process, time::Instant};

mod macros;
pub mod panic_context;

pub use crate::macros::{on_assert_failure, set_assert_hook};

#[inline(always)]
pub fn is_ci() -> bool {
    option_env!("CI").is_some()
}

#[must_use]
pub fn timeit(label: &'static str) -> impl Drop {
    struct Guard {
        label: &'static str,
        start: Instant,
    }

    impl Drop for Guard {
        fn drop(&mut self) {
            eprintln!("{}: {:.2?}", self.label, self.start.elapsed())
        }
    }

    Guard { label, start: Instant::now() }
}

/// Prints backtrace to stderr, useful for debugging.
#[cfg(feature = "backtrace")]
pub fn print_backtrace() {
    let bt = backtrace::Backtrace::new();
    eprintln!("{:?}", bt);
}
#[cfg(not(feature = "backtrace"))]
pub fn print_backtrace() {
    eprintln!(
        r#"Enable the backtrace feature.
Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
"#
    );
}

pub fn to_lower_snake_case(s: &str) -> String {
    to_snake_case(s, char::to_ascii_lowercase)
}
pub fn to_upper_snake_case(s: &str) -> String {
    to_snake_case(s, char::to_ascii_uppercase)
}
fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
    let mut buf = String::with_capacity(s.len());
    let mut prev = false;
    for c in s.chars() {
        // `&& prev` is required to not insert `_` before the first symbol.
        if c.is_ascii_uppercase() && prev {
            // This check is required to not translate `Weird_Case` into `weird__case`.
            if !buf.ends_with('_') {
                buf.push('_')
            }
        }
        prev = true;

        buf.push(change_case(&c));
    }
    buf
}

pub fn replace(buf: &mut String, from: char, to: &str) {
    if !buf.contains(from) {
        return;
    }
    // FIXME: do this in place.
    *buf = buf.replace(from, to)
}

// https://github.com/rust-lang/rust/issues/74773
pub fn split_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
    let mut split = haystack.splitn(2, delim);
    let prefix = split.next()?;
    let suffix = split.next()?;
    Some((prefix, suffix))
}
pub fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
    let mut split = haystack.rsplitn(2, delim);
    let suffix = split.next()?;
    let prefix = split.next()?;
    Some((prefix, suffix))
}

pub fn trim_indent(mut text: &str) -> String {
    if text.starts_with('\n') {
        text = &text[1..];
    }
    let indent = text
        .lines()
        .filter(|it| !it.trim().is_empty())
        .map(|it| it.len() - it.trim_start().len())
        .min()
        .unwrap_or(0);
    lines_with_ends(text)
        .map(
            |line| {
                if line.len() <= indent {
                    line.trim_start_matches(' ')
                } else {
                    &line[indent..]
                }
            },
        )
        .collect()
}

pub fn lines_with_ends(text: &str) -> LinesWithEnds {
    LinesWithEnds { text }
}

pub struct LinesWithEnds<'a> {
    text: &'a str,
}

impl<'a> Iterator for LinesWithEnds<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        if self.text.is_empty() {
            return None;
        }
        let idx = self.text.find('\n').map_or(self.text.len(), |it| it + 1);
        let (res, next) = self.text.split_at(idx);
        self.text = next;
        Some(res)
    }
}

/// Returns `idx` such that:
///
/// ```text
///     ∀ x in slice[..idx]:  pred(x)
///  && ∀ x in slice[idx..]: !pred(x)
/// ```
///
/// https://github.com/rust-lang/rust/issues/73831
pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
where
    P: FnMut(&T) -> bool,
{
    let mut left = 0;
    let mut right = slice.len();

    while left != right {
        let mid = left + (right - left) / 2;
        // SAFETY:
        // When left < right, left <= mid < right.
        // Therefore left always increases and right always decreases,
        // and either of them is selected.
        // In both cases left <= right is satisfied.
        // Therefore if left < right in a step,
        // left <= right is satisfied in the next step.
        // Therefore as long as left != right, 0 <= left < right <= len is satisfied
        // and if this case 0 <= mid < len is satisfied too.
        let value = unsafe { slice.get_unchecked(mid) };
        if pred(value) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }

    left
}

pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
where
    F: FnMut(&T) -> Ordering,
{
    let start = partition_point(slice, |it| key(it) == Ordering::Less);
    let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
    start..start + len
}

pub struct JodChild(pub process::Child);

impl ops::Deref for JodChild {
    type Target = process::Child;
    fn deref(&self) -> &process::Child {
        &self.0
    }
}

impl ops::DerefMut for JodChild {
    fn deref_mut(&mut self) -> &mut process::Child {
        &mut self.0
    }
}

impl Drop for JodChild {
    fn drop(&mut self) {
        let _ = self.0.kill();
    }
}

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

    #[test]
    fn test_trim_indent() {
        assert_eq!(trim_indent(""), "");
        assert_eq!(
            trim_indent(
                "
            hello
            world
"
            ),
            "hello\nworld\n"
        );
        assert_eq!(
            trim_indent(
                "
            hello
            world"
            ),
            "hello\nworld"
        );
        assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
        assert_eq!(
            trim_indent(
                "
            fn main() {
                return 92;
            }
        "
            ),
            "fn main() {\n    return 92;\n}\n"
        );
    }
}
<commit_msg>stave off zombies<commit_after>//! Missing batteries for standard libraries.
use std::{cmp::Ordering, ops, process, time::Instant};

mod macros;
pub mod panic_context;

pub use crate::macros::{on_assert_failure, set_assert_hook};

#[inline(always)]
pub fn is_ci() -> bool {
    option_env!("CI").is_some()
}

#[must_use]
pub fn timeit(label: &'static str) -> impl Drop {
    struct Guard {
        label: &'static str,
        start: Instant,
    }

    impl Drop for Guard {
        fn drop(&mut self) {
            eprintln!("{}: {:.2?}", self.label, self.start.elapsed())
        }
    }

    Guard { label, start: Instant::now() }
}

/// Prints backtrace to stderr, useful for debugging.
#[cfg(feature = "backtrace")]
pub fn print_backtrace() {
    let bt = backtrace::Backtrace::new();
    eprintln!("{:?}", bt);
}
#[cfg(not(feature = "backtrace"))]
pub fn print_backtrace() {
    eprintln!(
        r#"Enable the backtrace feature.
Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
"#
    );
}

pub fn to_lower_snake_case(s: &str) -> String {
    to_snake_case(s, char::to_ascii_lowercase)
}
pub fn to_upper_snake_case(s: &str) -> String {
    to_snake_case(s, char::to_ascii_uppercase)
}
fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
    let mut buf = String::with_capacity(s.len());
    let mut prev = false;
    for c in s.chars() {
        // `&& prev` is required to not insert `_` before the first symbol.
        if c.is_ascii_uppercase() && prev {
            // This check is required to not translate `Weird_Case` into `weird__case`.
            if !buf.ends_with('_') {
                buf.push('_')
            }
        }
        prev = true;

        buf.push(change_case(&c));
    }
    buf
}

pub fn replace(buf: &mut String, from: char, to: &str) {
    if !buf.contains(from) {
        return;
    }
    // FIXME: do this in place.
    *buf = buf.replace(from, to)
}

// https://github.com/rust-lang/rust/issues/74773
pub fn split_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
    let mut split = haystack.splitn(2, delim);
    let prefix = split.next()?;
    let suffix = split.next()?;
    Some((prefix, suffix))
}
pub fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
    let mut split = haystack.rsplitn(2, delim);
    let suffix = split.next()?;
    let prefix = split.next()?;
    Some((prefix, suffix))
}

pub fn trim_indent(mut text: &str) -> String {
    if text.starts_with('\n') {
        text = &text[1..];
    }
    let indent = text
        .lines()
        .filter(|it| !it.trim().is_empty())
        .map(|it| it.len() - it.trim_start().len())
        .min()
        .unwrap_or(0);
    lines_with_ends(text)
        .map(
            |line| {
                if line.len() <= indent {
                    line.trim_start_matches(' ')
                } else {
                    &line[indent..]
                }
            },
        )
        .collect()
}

pub fn lines_with_ends(text: &str) -> LinesWithEnds {
    LinesWithEnds { text }
}

pub struct LinesWithEnds<'a> {
    text: &'a str,
}

impl<'a> Iterator for LinesWithEnds<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        if self.text.is_empty() {
            return None;
        }
        let idx = self.text.find('\n').map_or(self.text.len(), |it| it + 1);
        let (res, next) = self.text.split_at(idx);
        self.text = next;
        Some(res)
    }
}

/// Returns `idx` such that:
///
/// ```text
///     ∀ x in slice[..idx]:  pred(x)
///  && ∀ x in slice[idx..]: !pred(x)
/// ```
///
/// https://github.com/rust-lang/rust/issues/73831
pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
where
    P: FnMut(&T) -> bool,
{
    let mut left = 0;
    let mut right = slice.len();

    while left != right {
        let mid = left + (right - left) / 2;
        // SAFETY:
        // When left < right, left <= mid < right.
        // Therefore left always increases and right always decreases,
        // and either of them is selected.
        // In both cases left <= right is satisfied.
        // Therefore if left < right in a step,
        // left <= right is satisfied in the next step.
        // Therefore as long as left != right, 0 <= left < right <= len is satisfied
        // and if this case 0 <= mid < len is satisfied too.
        let value = unsafe { slice.get_unchecked(mid) };
        if pred(value) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }

    left
}

pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
where
    F: FnMut(&T) -> Ordering,
{
    let start = partition_point(slice, |it| key(it) == Ordering::Less);
    let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
    start..start + len
}

pub struct JodChild(pub process::Child);

impl ops::Deref for JodChild {
    type Target = process::Child;
    fn deref(&self) -> &process::Child {
        &self.0
    }
}

impl ops::DerefMut for JodChild {
    fn deref_mut(&mut self) -> &mut process::Child {
        &mut self.0
    }
}

impl Drop for JodChild {
    fn drop(&mut self) {
        let _ = self.0.kill();
        let _ = self.0.wait();
    }
}

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

    #[test]
    fn test_trim_indent() {
        assert_eq!(trim_indent(""), "");
        assert_eq!(
            trim_indent(
                "
            hello
            world
"
            ),
            "hello\nworld\n"
        );
        assert_eq!(
            trim_indent(
                "
            hello
            world"
            ),
            "hello\nworld"
        );
        assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
        assert_eq!(
            trim_indent(
                "
            fn main() {
                return 92;
            }
        "
            ),
            "fn main() {\n    return 92;\n}\n"
        );
    }
}
<|endoftext|>


<commit_before>//! Selection sort algorithm.
//!
//! The `selection` module contains the simple sorting algorithm "Selection Sort".
//!
//! Source: https://en.wikipedia.org/wiki/Selection_sort

use std::fmt::Debug;

/// Simple sort: selection sort.
///
/// # Details
///
/// Selection sort is one of the simplest sorting algorithm. It sorts a vector in place by
/// looping over all elements and swapping the smallest element of the rest of the vector.
///
/// # Scaling
///
/// Selection sort in scale as O(N^2) since for all elements to sort, the algorithm will compare it
/// with every (remaining) elements.
///
/// Insertion sort is still useful as it is quite simple.
///
/// # Optimizations
///
/// None
///
/// # Notes
///
/// The type T of the vector elements to sort _must_ implement the `PartialOrd` trait so the
/// compiler knows how to compare the elements and sort them.
///
/// # Examples
///
/// ```
/// let mut data: Vec<i32> = vec![4, 2, 3, 1, 5];
/// sorting::simplesorts::selection::sort(&mut data);
/// assert_eq!(vec![1, 2, 3, 4, 5], data);
/// ```
///
pub fn sort<T: PartialOrd + Debug>(array: &mut Vec<T>) {
    println!("input array:  {:?}", array);

    if !array.is_empty() {
        // Let's loop over the input array, skipping last value (important as we want to get a
        // slice of the following values).
        let n = array.len();
        for i in 0..n-1 {
            println!("    i: {:?}", i);

            // Find index of the minimum value in the slice of values following the i^th element.
            let j = {
                let int_slice = &array[i+1..];
                println!("        array:        {:?}", array);
                println!("        int_slice:    {:?}", int_slice);
                // k now holds the index (of "array") of the minimum element of "int_slice", or the
                // value "i" of the current element of "array" (which should be the less than
                // all elements of "int_slice").
                // "k" is a tuple containing the smallest element of "int_slice" and its index.
                //     k.0: Index of "int_slice" of its smallest value.
                //     k.1: Smallest value of "int_slice".
                let k = int_slice.iter().enumerate().fold(
                    (0, int_slice.first().unwrap()), |acc, item| {
                        if acc.1 > item.1 { item } else { acc }
                });

                // Return from the block the smallest element between the one at index "i" of
                // the "array" and the smallest one from the "int_slice" array.
                // NOTE: Since "k" worked on the subarray "int_slice", it's first element is the
                //       i^th element of "array". If "k" is actually smaller than "array[i]", we
                //       need to adapt the index as stored in "k.0".
                if k.1 < &array[i] { i + k.0 + 1 } else { i }
            };
            println!("            j: {:?}   array[i]={:?}   array[j]={:?}", j, array[i], array[j]);

            array.swap(i, j);
            println!("        new array:    {:?}", array);
        }
    }

    println!("output array: {:?}", array);
}
<commit_msg>Cleanup: Remove debug prints.<commit_after>//! Selection sort algorithm.
//!
//! The `selection` module contains the simple sorting algorithm "Selection Sort".
//!
//! Source: https://en.wikipedia.org/wiki/Selection_sort

/// Simple sort: selection sort.
///
/// # Details
///
/// Selection sort is one of the simplest sorting algorithm. It sorts a vector in place by
/// looping over all elements and swapping the smallest element of the rest of the vector.
///
/// # Scaling
///
/// Selection sort in scale as O(N^2) since for all elements to sort, the algorithm will compare it
/// with every (remaining) elements.
///
/// Insertion sort is still useful as it is quite simple.
///
/// # Optimizations
///
/// None
///
/// # Notes
///
/// The type T of the vector elements to sort _must_ implement the `PartialOrd` trait so the
/// compiler knows how to compare the elements and sort them.
///
/// # Examples
///
/// ```
/// let mut data: Vec<i32> = vec![4, 2, 3, 1, 5];
/// sorting::simplesorts::selection::sort(&mut data);
/// assert_eq!(vec![1, 2, 3, 4, 5], data);
/// ```
///
pub fn sort<T: PartialOrd>(array: &mut Vec<T>) {

    if !array.is_empty() {
        // Let's loop over the input array, skipping last value (important as we want to get a
        // slice of the following values).
        let n = array.len();
        for i in 0..n-1 {
            // Find index of the minimum value in the slice of values following the i^th element.
            let j = {
                let int_slice = &array[i+1..];
                // k now holds the index (of "array") of the minimum element of "int_slice", or the
                // value "i" of the current element of "array" (which should be the less than
                // all elements of "int_slice").
                // "k" is a tuple containing the smallest element of "int_slice" and its index.
                //     k.0: Index of "int_slice" of its smallest value.
                //     k.1: Smallest value of "int_slice".
                let k = int_slice.iter().enumerate().fold(
                    (0, int_slice.first().unwrap()), |acc, item| {
                        if acc.1 > item.1 { item } else { acc }
                });

                // Return from the block the smallest element between the one at index "i" of
                // the "array" and the smallest one from the "int_slice" array.
                // NOTE: Since "k" worked on the subarray "int_slice", it's first element is the
                //       i^th element of "array". If "k" is actually smaller than "array[i]", we
                //       need to adapt the index as stored in "k.0".
                if k.1 < &array[i] { i + k.0 + 1 } else { i }
            };

            array.swap(i, j);
        }
    }
}
<|endoftext|>