text:
use std::error::Error;
use std::fmt;
use std::io::Write;
use std::iter;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;

use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, Shell, SubCommand};

use super::common;
use super::errors::*;
use super::help::*;
use super::self_update;
use super::term2;
use super::term2::Terminal;
use super::topical_doc;
use crate::dist::dist::{PartialTargetTriple, PartialToolchainDesc, Profile, TargetTriple};
use crate::dist::manifest::Component;
use crate::process;
use crate::toolchain::{CustomToolchain, DistributableToolchain};
use crate::utils::utils;
use crate::Notification;
use crate::{command, Cfg, ComponentStatus, Toolchain};

fn handle_epipe(res: Result<utils::ExitCode>) -> Result<utils::ExitCode> {
    match res {
        Err(Error(ErrorKind::Io(ref err), _)) if err.kind() == std::io::ErrorKind::BrokenPipe => {
            Ok(utils::ExitCode(0))
        }
        res => res,
    }
}

fn deprecated<F, B>(instead: &str, cfg: &mut Cfg, matches: B, callee: F) -> Result<utils::ExitCode>
where
    F: FnOnce(&mut Cfg, B) -> Result<utils::ExitCode>,
{
    (cfg.notify_handler)(Notification::PlainVerboseMessage(
        "Use of (currently) unmaintained command line interface.",
    ));
    (cfg.notify_handler)(Notification::PlainVerboseMessage(
        "The exact API of this command may change without warning",
    ));
    (cfg.notify_handler)(Notification::PlainVerboseMessage(
        "Eventually this command will be a true alias.  Until then:",
    ));
    (cfg.notify_handler)(Notification::PlainVerboseMessage(&format!(
        "  Please use `rustup {}` instead",
        instead
    )));
    callee(cfg, matches)
}

pub fn main() -> Result<utils::ExitCode> {
    self_update::cleanup_self_updater()?;

    let matches = match cli().get_matches_from_safe(process().args_os()) {
        Ok(matches) => Ok(matches),
        Err(e)
            if e.kind == clap::ErrorKind::HelpDisplayed
                || e.kind == clap::ErrorKind::VersionDisplayed =>
        {
            writeln!(process().stdout().lock(), "{}", e.message)?;
            return Ok(utils::ExitCode(0));
        }
        Err(e) => Err(e),
    }?;
    let verbose = matches.is_present("verbose");
    let quiet = matches.is_present("quiet");
    let cfg = &mut common::set_globals(verbose, quiet)?;

    if let Some(t) = matches.value_of("+toolchain") {
        cfg.set_toolchain_override(&t[1..]);
    }

    if maybe_upgrade_data(cfg, &matches)? {
        return Ok(utils::ExitCode(0));
    }

    cfg.check_metadata_version()?;

    Ok(match matches.subcommand() {
        ("dump-testament", _) => common::dump_testament()?,
        ("show", Some(c)) => match c.subcommand() {
            ("active-toolchain", Some(_)) => handle_epipe(show_active_toolchain(cfg))?,
            ("home", Some(_)) => handle_epipe(show_rustup_home(cfg))?,
            ("profile", Some(_)) => handle_epipe(show_profile(cfg))?,
            ("keys", Some(_)) => handle_epipe(show_keys(cfg))?,
            (_, _) => handle_epipe(show(cfg))?,
        },
        ("install", Some(m)) => deprecated("toolchain install", cfg, m, update)?,
        ("update", Some(m)) => update(cfg, m)?,
        ("check", Some(_)) => check_updates(cfg)?,
        ("uninstall", Some(m)) => deprecated("toolchain uninstall", cfg, m, toolchain_remove)?,
        ("default", Some(m)) => default_(cfg, m)?,
        ("toolchain", Some(c)) => match c.subcommand() {
            ("install", Some(m)) => update(cfg, m)?,
            ("list", Some(m)) => handle_epipe(toolchain_list(cfg, m))?,
            ("link", Some(m)) => toolchain_link(cfg, m)?,
            ("uninstall", Some(m)) => toolchain_remove(cfg, m)?,
            (_, _) => unreachable!(),
        },
        ("target", Some(c)) => match c.subcommand() {
            ("list", Some(m)) => handle_epipe(target_list(cfg, m))?,
            ("add", Some(m)) => target_add(cfg, m)?,
            ("remove", Some(m)) => target_remove(cfg, m)?,
            (_, _) => unreachable!(),
        },
        ("component", Some(c)) => match c.subcommand() {
            ("list", Some(m)) => handle_epipe(component_list(cfg, m))?,
            ("add", Some(m)) => component_add(cfg, m)?,
            ("remove", Some(m)) => component_remove(cfg, m)?,
            (_, _) => unreachable!(),
        },
        ("override", Some(c)) => match c.subcommand() {
            ("list", Some(_)) => handle_epipe(common::list_overrides(cfg))?,
            ("set", Some(m)) => override_add(cfg, m)?,
            ("unset", Some(m)) => override_remove(cfg, m)?,
            (_, _) => unreachable!(),
        },
        ("run", Some(m)) => run(cfg, m)?,
        ("which", Some(m)) => which(cfg, m)?,
        ("doc", Some(m)) => doc(cfg, m)?,
        ("man", Some(m)) => man(cfg, m)?,
        ("self", Some(c)) => match c.subcommand() {
            ("update", Some(_)) => self_update::update(cfg)?,
            ("uninstall", Some(m)) => self_uninstall(m)?,
            (_, _) => unreachable!(),
        },
        ("set", Some(c)) => match c.subcommand() {
            ("default-host", Some(m)) => set_default_host_triple(cfg, m)?,
            ("profile", Some(m)) => set_profile(cfg, m)?,
            (_, _) => unreachable!(),
        },
        ("completions", Some(c)) => {
            if let Some(shell) = c.value_of("shell") {
                output_completion_script(
                    shell.parse::<Shell>().unwrap(),
                    c.value_of("command")
                        .and_then(|cmd| cmd.parse::<CompletionCommand>().ok())
                        .unwrap_or(CompletionCommand::Rustup),
                )?
            } else {
                unreachable!()
            }
        }
        (_, _) => unreachable!(),
    })
}

pub fn cli() -> App<'static, 'static> {
    let mut app = App::new("rustup")
        .version(common::version())
        .about("The Rust toolchain installer")
        .after_help(RUSTUP_HELP)
        .setting(AppSettings::VersionlessSubcommands)
        .setting(AppSettings::DeriveDisplayOrder)
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .arg(
            Arg::with_name("verbose")
                .help("Enable verbose output")
                .short("v")
                .long("verbose"),
        )
        .arg(
            Arg::with_name("quiet")
                .conflicts_with("verbose")
                .help("Disable progress output")
                .short("q")
                .long("quiet"),
        )
        .arg(
            Arg::with_name("+toolchain")
                .help("release channel (e.g. +stable) or custom toolchain to set override")
                .validator(|s| {
                    if s.starts_with('+') {
                        Ok(())
                    } else {
                        Err("Toolchain overrides must begin with '+'".into())
                    }
                }),
        )
        .subcommand(
            SubCommand::with_name("dump-testament")
                .about("Dump information about the build")
                .setting(AppSettings::Hidden), // Not for users, only CI
        )
        .subcommand(
            SubCommand::with_name("show")
                .about("Show the active and installed toolchains or profiles")
                .after_help(SHOW_HELP)
                .setting(AppSettings::VersionlessSubcommands)
                .setting(AppSettings::DeriveDisplayOrder)
                .subcommand(
                    SubCommand::with_name("active-toolchain")
                        .about("Show the active toolchain")
                        .after_help(SHOW_ACTIVE_TOOLCHAIN_HELP),
                )
                .subcommand(
                    SubCommand::with_name("home")
                        .about("Display the computed value of RUSTUP_HOME"),
                )
                .subcommand(SubCommand::with_name("profile").about("Show the current profile"))
                .subcommand(SubCommand::with_name("keys").about("Display the known PGP keys")),
        )
        .subcommand(
            SubCommand::with_name("install")
                .about("Update Rust toolchains")
                .after_help(INSTALL_HELP)
                .setting(AppSettings::Hidden) // synonym for 'toolchain install'
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .required(true)
                        .multiple(true),
                )
                .arg(
                    Arg::with_name("profile")
                        .long("profile")
                        .takes_value(true)
                        .possible_values(Profile::names())
                        .required(false),
                )
                .arg(
                    Arg::with_name("no-self-update")
                        .help("Don't perform self-update when running the `rustup install` command")
                        .long("no-self-update")
                        .takes_value(false),
                )
                .arg(
                    Arg::with_name("force")
                        .help("Force an update, even if some components are missing")
                        .long("force")
                        .takes_value(false),
                ),
        )
        .subcommand(
            SubCommand::with_name("uninstall")
                .about("Uninstall Rust toolchains")
                .setting(AppSettings::Hidden) // synonym for 'toolchain uninstall'
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .required(true)
                        .multiple(true),
                ),
        )
        .subcommand(
            SubCommand::with_name("update")
                .about("Update Rust toolchains and rustup")
                .after_help(UPDATE_HELP)
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .required(false)
                        .multiple(true),
                )
                .arg(
                    Arg::with_name("no-self-update")
                        .help("Don't perform self update when running the `rustup update` command")
                        .long("no-self-update")
                        .takes_value(false),
                )
                .arg(
                    Arg::with_name("force")
                        .help("Force an update, even if some components are missing")
                        .long("force")
                        .takes_value(false),
                ),
        )
        .subcommand(SubCommand::with_name("check").about("Check for updates to Rust toolchains"))
        .subcommand(
            SubCommand::with_name("default")
                .about("Set the default toolchain")
                .after_help(DEFAULT_HELP)
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .required(false),
                ),
        )
        .subcommand(
            SubCommand::with_name("toolchain")
                .about("Modify or query the installed toolchains")
                .after_help(TOOLCHAIN_HELP)
                .setting(AppSettings::VersionlessSubcommands)
                .setting(AppSettings::DeriveDisplayOrder)
                .setting(AppSettings::SubcommandRequiredElseHelp)
                .subcommand(
                    SubCommand::with_name("list")
                        .about("List installed toolchains")
                        .arg(
                            Arg::with_name("verbose")
                                .help("Enable verbose output with toolchain information")
                                .takes_value(false)
                                .short("v")
                                .long("verbose"),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("install")
                        .about("Install or update a given toolchain")
                        .aliases(&["update", "add"])
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .required(true)
                                .multiple(true),
                        )
                        .arg(
                            Arg::with_name("profile")
                                .long("profile")
                                .takes_value(true)
                                .possible_values(Profile::names())
                                .required(false),
                        )
                        .arg(
                            Arg::with_name("no-self-update")
                                .help(
                                    "Don't perform self update when running the\
                                     `rustup toolchain install` command",
                                )
                                .long("no-self-update")
                                .takes_value(false),
                        )
                        .arg(
                            Arg::with_name("components")
                                .help("Add specific components on installation")
                                .long("component")
                                .short("c")
                                .takes_value(true)
                                .multiple(true)
                                .use_delimiter(true),
                        )
                        .arg(
                            Arg::with_name("targets")
                                .help("Add specific targets on installation")
                                .long("target")
                                .short("t")
                                .takes_value(true)
                                .multiple(true)
                                .use_delimiter(true),
                        )
                        .arg(
                            Arg::with_name("force")
                                .help("Force an update, even if some components are missing")
                                .long("force")
                                .takes_value(false),
                        )
                        .arg(
                            Arg::with_name("allow-downgrade")
                                .help("Allow rustup to downgrade the toolchain to satisfy your component choice")
                                .long("allow-downgrade")
                                .takes_value(false),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("uninstall")
                        .about("Uninstall a toolchain")
                        .alias("remove")
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .required(true)
                                .multiple(true),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("link")
                        .about("Create a custom toolchain by symlinking to a directory")
                        .after_help(TOOLCHAIN_LINK_HELP)
                        .arg(
                            Arg::with_name("toolchain")
                                .help("Custom toolchain name")
                                .required(true),
                        )
                        .arg(
                            Arg::with_name("path")
                                .help("Path to the directory")
                                .required(true),
                        ),
                ),
        )
        .subcommand(
            SubCommand::with_name("target")
                .about("Modify a toolchain's supported targets")
                .setting(AppSettings::VersionlessSubcommands)
                .setting(AppSettings::DeriveDisplayOrder)
                .setting(AppSettings::SubcommandRequiredElseHelp)
                .subcommand(
                    SubCommand::with_name("list")
                        .about("List installed and available targets")
                        .arg(
                            Arg::with_name("installed")
                                .long("--installed")
                                .help("List only installed targets"),
                        )
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .long("toolchain")
                                .takes_value(true),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("add")
                        .about("Add a target to a Rust toolchain")
                        .alias("install")
                        .arg(Arg::with_name("target").required(true).multiple(true).help(
                            "List of targets to install; \
                             \"all\" installs all available targets",
                        ))
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .long("toolchain")
                                .takes_value(true),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("remove")
                        .about("Remove a target from a Rust toolchain")
                        .alias("uninstall")
                        .arg(Arg::with_name("target").required(true).multiple(true))
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .long("toolchain")
                                .takes_value(true),
                        ),
                ),
        )
        .subcommand(
            SubCommand::with_name("component")
                .about("Modify a toolchain's installed components")
                .setting(AppSettings::VersionlessSubcommands)
                .setting(AppSettings::DeriveDisplayOrder)
                .setting(AppSettings::SubcommandRequiredElseHelp)
                .subcommand(
                    SubCommand::with_name("list")
                        .about("List installed and available components")
                        .arg(
                            Arg::with_name("installed")
                                .long("--installed")
                                .help("List only installed components"),
                        )
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .long("toolchain")
                                .takes_value(true),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("add")
                        .about("Add a component to a Rust toolchain")
                        .arg(Arg::with_name("component").required(true).multiple(true))
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .long("toolchain")
                                .takes_value(true),
                        )
                        .arg(Arg::with_name("target").long("target").takes_value(true)),
                )
                .subcommand(
                    SubCommand::with_name("remove")
                        .about("Remove a component from a Rust toolchain")
                        .arg(Arg::with_name("component").required(true).multiple(true))
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .long("toolchain")
                                .takes_value(true),
                        )
                        .arg(Arg::with_name("target").long("target").takes_value(true)),
                ),
        )
        .subcommand(
            SubCommand::with_name("override")
                .about("Modify directory toolchain overrides")
                .after_help(OVERRIDE_HELP)
                .setting(AppSettings::VersionlessSubcommands)
                .setting(AppSettings::DeriveDisplayOrder)
                .setting(AppSettings::SubcommandRequiredElseHelp)
                .subcommand(
                    SubCommand::with_name("list").about("List directory toolchain overrides"),
                )
                .subcommand(
                    SubCommand::with_name("set")
                        .about("Set the override toolchain for a directory")
                        .alias("add")
                        .arg(
                            Arg::with_name("toolchain")
                                .help(TOOLCHAIN_ARG_HELP)
                                .required(true),
                        )
                        .arg(
                            Arg::with_name("path")
                                .long("path")
                                .takes_value(true)
                                .help("Path to the directory"),
                        ),
                )
                .subcommand(
                    SubCommand::with_name("unset")
                        .about("Remove the override toolchain for a directory")
                        .after_help(OVERRIDE_UNSET_HELP)
                        .alias("remove")
                        .arg(
                            Arg::with_name("path")
                                .long("path")
                                .takes_value(true)
                                .help("Path to the directory"),
                        )
                        .arg(
                            Arg::with_name("nonexistent")
                                .long("nonexistent")
                                .takes_value(false)
                                .help("Remove override toolchain for all nonexistent directories"),
                        ),
                ),
        )
        .subcommand(
            SubCommand::with_name("run")
                .about("Run a command with an environment configured for a given toolchain")
                .after_help(RUN_HELP)
                .setting(AppSettings::TrailingVarArg)
                .arg(
                    Arg::with_name("install")
                        .help("Install the requested toolchain if needed")
                        .long("install"),
                )
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .required(true),
                )
                .arg(
                    Arg::with_name("command")
                        .required(true)
                        .multiple(true)
                        .use_delimiter(false),
                ),
        )
        .subcommand(
            SubCommand::with_name("which")
                .about("Display which binary will be run for a given command")
                .arg(Arg::with_name("command").required(true))
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .long("toolchain")
                        .takes_value(true),
                ),
        )
        .subcommand(
            SubCommand::with_name("doc")
                .alias("docs")
                .about("Open the documentation for the current toolchain")
                .after_help(DOC_HELP)
                .arg(
                    Arg::with_name("path")
                        .long("path")
                        .help("Only print the path to the documentation"),
                )
                .args(
                    &DOCS_DATA
                        .iter()
                        .map(|(name, help_msg, _)| Arg::with_name(name).long(name).help(help_msg))
                        .collect::<Vec<_>>(),
                )
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .long("toolchain")
                        .takes_value(true),
                )
                .group(
                    ArgGroup::with_name("page").args(
                        &DOCS_DATA
                            .iter()
                            .map(|(name, _, _)| *name)
                            .collect::<Vec<_>>(),
                    ),
                )
                .arg(Arg::with_name("topic").help(TOPIC_ARG_HELP)),
        );

    if cfg!(not(target_os = "windows")) {
        app = app.subcommand(
            SubCommand::with_name("man")
                .about("View the man page for a given command")
                .arg(Arg::with_name("command").required(true))
                .arg(
                    Arg::with_name("toolchain")
                        .help(TOOLCHAIN_ARG_HELP)
                        .long("toolchain")
                        .takes_value(true),
                ),
        );
    }

    app = app
        .subcommand(
            SubCommand::with_name("self")
                .about("Modify the rustup installation")
                .setting(AppSettings::VersionlessSubcommands)
                .setting(AppSettings::DeriveDisplayOrder)
                .setting(AppSettings::SubcommandRequiredElseHelp)
                .subcommand(
                    SubCommand::with_name("update").about("Download and install updates to rustup"),
                )
                .subcommand(
                    SubCommand::with_name("uninstall")
                        .about("Uninstall rustup.")
                        .arg(Arg::with_name("no-prompt").short("y")),
                )
                .subcommand(
                    SubCommand::with_name("upgrade-data")
                        .about("Upgrade the internal data format."),
                ),
        )
        .subcommand(
            SubCommand::with_name("set")
                .about("Alter rustup settings")
                .setting(AppSettings::SubcommandRequiredElseHelp)
                .subcommand(
                    SubCommand::with_name("default-host")
                        .about("The triple used to identify toolchains when not specified")
                        .arg(Arg::with_name("host_triple").required(true)),
                )
                .subcommand(
                    SubCommand::with_name("profile")
                        .about("The default components installed")
                        .arg(
                            Arg::with_name("profile-name")
                                .required(true)
                                .possible_values(Profile::names())
                                .default_value(Profile::default_name()),
                        ),
                ),
        );

    // Clap provides no good way to say that help should be printed in all
    // cases where an argument without a default is not provided. The following
    // creates lists out all the conditions where the "shell" argument are
    // provided and give the default of "rustup". This way if "shell" is not
    // provided then the help will still be printed.
    let completion_defaults = Shell::variants()
        .iter()
        .map(|&shell| ("shell", Some(shell), "rustup"))
        .collect::<Vec<_>>();

    app.subcommand(
        SubCommand::with_name("completions")
            .about("Generate tab-completion scripts for your shell")
            .after_help(COMPLETIONS_HELP)
            .setting(AppSettings::ArgRequiredElseHelp)
            .arg(Arg::with_name("shell").possible_values(&Shell::variants()))
            .arg(
                Arg::with_name("command")
                    .possible_values(&CompletionCommand::variants())
                    .default_value_ifs(&completion_defaults[..]),
            ),
    )
}

fn maybe_upgrade_data(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<bool> {
    match m.subcommand() {
        ("self", Some(c)) => match c.subcommand() {
            ("upgrade-data", Some(_)) => {
                cfg.upgrade_data()?;
                Ok(true)
            }
            _ => Ok(false),
        },
        _ => Ok(false),
    }
}

fn update_bare_triple_check(cfg: &Cfg, name: &str) -> Result<()> {
    if let Some(triple) = PartialTargetTriple::new(name) {
        warn!("(partial) target triple specified instead of toolchain name");
        let installed_toolchains = cfg.list_toolchains()?;
        let default = cfg.find_default()?;
        let default_name = default.map(|t| t.name().to_string()).unwrap_or_default();
        let mut candidates = vec![];
        for t in installed_toolchains {
            if t == default_name {
                continue;
            }
            if let Ok(desc) = PartialToolchainDesc::from_str(&t) {
                fn triple_comp_eq(given: &str, from_desc: Option<&String>) -> bool {
                    from_desc.map_or(false, |s| *s == *given)
                }

                let triple_matches = triple
                    .arch
                    .as_ref()
                    .map_or(true, |s| triple_comp_eq(s, desc.target.arch.as_ref()))
                    && triple
                        .os
                        .as_ref()
                        .map_or(true, |s| triple_comp_eq(s, desc.target.os.as_ref()))
                    && triple
                        .env
                        .as_ref()
                        .map_or(true, |s| triple_comp_eq(s, desc.target.env.as_ref()));
                if triple_matches {
                    candidates.push(t);
                }
            }
        }
        match candidates.len() {
            0 => err!("no candidate toolchains found"),
            1 => writeln!(
                process().stdout(),
                "\nyou may use the following toolchain: {}\n",
                candidates[0]
            )?,
            _ => {
                writeln!(
                    process().stdout(),
                    "\nyou may use one of the following toolchains:"
                )?;
                for n in &candidates {
                    writeln!(process().stdout(), "{}", n)?;
                }
                writeln!(process().stdout(),)?;
            }
        }
        return Err(ErrorKind::ToolchainNotInstalled(name.to_string()).into());
    }
    Ok(())
}

fn default_bare_triple_check(cfg: &Cfg, name: &str) -> Result<()> {
    if let Some(triple) = PartialTargetTriple::new(name) {
        warn!("(partial) target triple specified instead of toolchain name");
        let default = cfg.find_default()?;
        let default_name = default.map(|t| t.name().to_string()).unwrap_or_default();
        if let Ok(mut desc) = PartialToolchainDesc::from_str(&default_name) {
            desc.target = triple;
            let maybe_toolchain = format!("{}", desc);
            let toolchain = cfg.get_toolchain(maybe_toolchain.as_ref(), false)?;
            if toolchain.name() == default_name {
                warn!(
                    "(partial) triple '{}' resolves to a toolchain that is already default",
                    name
                );
            } else {
                writeln!(
                    process().stdout(),
                    "\nyou may use the following toolchain: {}\n",
                    toolchain.name()
                )?;
            }
            return Err(ErrorKind::ToolchainNotInstalled(name.to_string()).into());
        }
    }
    Ok(())
}

fn default_(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    if m.is_present("toolchain") {
        let toolchain = m.value_of("toolchain").unwrap();
        default_bare_triple_check(cfg, toolchain)?;
        let toolchain = cfg.get_toolchain(toolchain, false)?;

        let status = if !toolchain.is_custom() {
            let distributable = DistributableToolchain::new(&toolchain)?;
            Some(distributable.install_from_dist_if_not_installed()?)
        } else if !toolchain.exists() {
            return Err(ErrorKind::ToolchainNotInstalled(toolchain.name().to_string()).into());
        } else {
            None
        };

        toolchain.make_default()?;

        if let Some(status) = status {
            writeln!(process().stdout())?;
            common::show_channel_update(cfg, toolchain.name(), Ok(status))?;
        }

        let cwd = utils::current_dir()?;
        if let Some((toolchain, reason)) = cfg.find_override(&cwd)? {
            info!(
                "note that the toolchain '{}' is currently in use ({})",
                toolchain.name(),
                reason
            );
        }
    } else {
        let default_toolchain: Result<String> = cfg
            .get_default()?
            .ok_or_else(|| "no default toolchain configured".into());
        writeln!(process().stdout(), "{} (default)", default_toolchain?)?;
    }

    Ok(utils::ExitCode(0))
}

fn check_updates(cfg: &Cfg) -> Result<utils::ExitCode> {
    let mut t = term2::stdout();
    let channels = cfg.list_channels()?;

    for channel in channels {
        match channel {
            (ref name, Ok(ref toolchain)) => {
                let distributable = DistributableToolchain::new(&toolchain)?;
                let current_version = distributable.show_version()?;
                let dist_version = distributable.show_dist_version()?;
                let _ = t.attr(term2::Attr::Bold);
                write!(t, "{} - ", name)?;
                match (current_version, dist_version) {
                    (None, None) => {
                        let _ = t.fg(term2::color::RED);
                        writeln!(t, "Cannot identify installed or update versions")?;
                    }
                    (Some(cv), None) => {
                        let _ = t.fg(term2::color::GREEN);
                        write!(t, "Up to date")?;
                        let _ = t.reset();
                        writeln!(t, " : {}", cv)?;
                    }
                    (Some(cv), Some(dv)) => {
                        let _ = t.fg(term2::color::YELLOW);
                        write!(t, "Update available")?;
                        let _ = t.reset();
                        writeln!(t, " : {} -> {}", cv, dv)?;
                    }
                    (None, Some(dv)) => {
                        let _ = t.fg(term2::color::YELLOW);
                        write!(t, "Update available")?;
                        let _ = t.reset();
                        writeln!(t, " : (Unknown version) -> {}", dv)?;
                    }
                }
            }
            (_, Err(err)) => return Err(err.into()),
        }
    }
    Ok(utils::ExitCode(0))
}

fn update(cfg: &mut Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let self_update = !m.is_present("no-self-update") && !self_update::NEVER_SELF_UPDATE;
    if let Some(p) = m.value_of("profile") {
        let p = Profile::from_str(p)?;
        cfg.set_profile_override(p);
    }
    let cfg = &cfg;
    if cfg.get_profile()? == Profile::Complete {
        warn!("{}", common::WARN_COMPLETE_PROFILE);
    }
    if let Some(names) = m.values_of("toolchain") {
        for name in names {
            update_bare_triple_check(cfg, name)?;
            let toolchain = cfg.get_toolchain(name, false)?;

            let status = if !toolchain.is_custom() {
                let components: Vec<_> = m
                    .values_of("components")
                    .map(|v| v.collect())
                    .unwrap_or_else(Vec::new);
                let targets: Vec<_> = m
                    .values_of("targets")
                    .map(|v| v.collect())
                    .unwrap_or_else(Vec::new);
                let distributable = DistributableToolchain::new(&toolchain)?;
                Some(distributable.install_from_dist(
                    m.is_present("force"),
                    m.is_present("allow-downgrade"),
                    &components,
                    &targets,
                )?)
            } else if !toolchain.exists() {
                return Err(ErrorKind::InvalidToolchainName(toolchain.name().to_string()).into());
            } else {
                None
            };

            if let Some(status) = status.clone() {
                writeln!(process().stdout())?;
                common::show_channel_update(cfg, toolchain.name(), Ok(status))?;
            }

            if cfg.get_default()?.is_none() {
                use crate::UpdateStatus;
                if let Some(UpdateStatus::Installed) = status {
                    toolchain.make_default()?;
                }
            }
        }
        if self_update {
            common::self_update(|| Ok(utils::ExitCode(0)))?;
        }
    } else {
        common::update_all_channels(cfg, self_update, m.is_present("force"))?;
        info!("cleaning up downloads & tmp directories");
        utils::delete_dir_contents(&cfg.download_dir);
        cfg.temp_cfg.clean();
    }

    Ok(utils::ExitCode(0))
}

fn run(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = m.value_of("toolchain").unwrap();
    let args = m.values_of("command").unwrap();
    let args: Vec<_> = args.collect();
    let cmd = cfg.create_command_for_toolchain(toolchain, m.is_present("install"), args[0])?;

    let code = command::run_command_for_dir(cmd, args[0], &args[1..])?;
    Ok(code)
}

fn which(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let binary = m.value_of("command").unwrap();
    let binary_path = if m.is_present("toolchain") {
        let toolchain = m.value_of("toolchain").unwrap();
        cfg.which_binary_by_toolchain(toolchain, binary)?
            .expect("binary not found")
    } else {
        cfg.which_binary(&utils::current_dir()?, binary)?
            .expect("binary not found")
    };

    utils::assert_is_file(&binary_path)?;

    writeln!(process().stdout(), "{}", binary_path.display())?;
    Ok(utils::ExitCode(0))
}

fn show(cfg: &Cfg) -> Result<utils::ExitCode> {
    // Print host triple
    {
        let mut t = term2::stdout();
        t.attr(term2::Attr::Bold)?;
        write!(t, "Default host: ")?;
        t.reset()?;
        writeln!(t, "{}", cfg.get_default_host_triple()?)?;
    }

    // Print rustup home directory
    {
        let mut t = term2::stdout();
        t.attr(term2::Attr::Bold)?;
        write!(t, "rustup home:  ")?;
        t.reset()?;
        writeln!(t, "{}", cfg.rustup_dir.display())?;
        writeln!(t)?;
    }

    let cwd = utils::current_dir()?;
    let installed_toolchains = cfg.list_toolchains()?;
    // XXX: we may want a find_without_install capability for show.
    let active_toolchain = cfg.find_or_install_override_toolchain_or_default(&cwd);

    // active_toolchain will carry the reason we don't have one in its detail.
    let active_targets = if let Ok(ref at) = active_toolchain {
        if let Ok(distributable) = DistributableToolchain::new(&at.0) {
            match distributable.list_components() {
                Ok(cs_vec) => cs_vec
                    .into_iter()
                    .filter(|c| c.component.short_name_in_manifest() == "rust-std")
                    .filter(|c| c.installed)
                    .collect(),
                Err(_) => vec![],
            }
        } else {
            // These three vec![] could perhaps be reduced with and_then on active_toolchain.
            vec![]
        }
    } else {
        vec![]
    };

    let show_installed_toolchains = installed_toolchains.len() > 1;
    let show_active_targets = active_targets.len() > 1;
    let show_active_toolchain = true;

    // Only need to display headers if we have multiple sections
    let show_headers = [
        show_installed_toolchains,
        show_active_targets,
        show_active_toolchain,
    ]
    .iter()
    .filter(|x| **x)
    .count()
        > 1;

    if show_installed_toolchains {
        let mut t = term2::stdout();
        if show_headers {
            print_header(&mut t, "installed toolchains")?;
        }
        let default_name: Result<String> = cfg
            .get_default()?
            .ok_or_else(|| "no default toolchain configured".into());
        let default_name = default_name?;
        for it in installed_toolchains {
            if default_name == it {
                writeln!(t, "{} (default)", it)?;
            } else {
                writeln!(t, "{}", it)?;
            }
        }
        if show_headers {
            writeln!(t)?
        };
    }

    if show_active_targets {
        let mut t = term2::stdout();
        if show_headers {
            print_header(&mut t, "installed targets for active toolchain")?;
        }
        for at in active_targets {
            writeln!(
                t,
                "{}",
                at.component
                    .target
                    .as_ref()
                    .expect("rust-std should have a target")
            )?;
        }
        if show_headers {
            writeln!(t)?;
        };
    }

    if show_active_toolchain {
        let mut t = term2::stdout();
        if show_headers {
            print_header(&mut t, "active toolchain")?;
        }

        match active_toolchain {
            Ok(atc) => match atc {
                (ref toolchain, Some(ref reason)) => {
                    writeln!(t, "{} ({})", toolchain.name(), reason)?;
                    writeln!(t, "{}", toolchain.rustc_version())?;
                }
                (ref toolchain, None) => {
                    writeln!(t, "{} (default)", toolchain.name())?;
                    writeln!(t, "{}", toolchain.rustc_version())?;
                }
            },
            Err(crate::Error(crate::ErrorKind::ToolchainNotSelected, _)) => {
                writeln!(t, "no active toolchain")?;
            }
            Err(err) => {
                if let Some(cause) = err.source() {
                    writeln!(t, "(error: {}, {})", err, cause)?;
                } else {
                    writeln!(t, "(error: {})", err)?;
                }
            }
        }

        if show_headers {
            writeln!(t)?
        }
    }

    fn print_header(t: &mut term2::StdoutTerminal, s: &str) -> Result<()> {
        t.attr(term2::Attr::Bold)?;
        writeln!(t, "{}", s)?;
        writeln!(t, "{}", iter::repeat("-").take(s.len()).collect::<String>())?;
        writeln!(t)?;
        t.reset()?;
        Ok(())
    }

    Ok(utils::ExitCode(0))
}

fn show_active_toolchain(cfg: &Cfg) -> Result<utils::ExitCode> {
    let cwd = utils::current_dir()?;
    match cfg.find_or_install_override_toolchain_or_default(&cwd) {
        Err(crate::Error(crate::ErrorKind::ToolchainNotSelected, _)) => {}
        Err(e) => return Err(e.into()),
        Ok((toolchain, reason)) => {
            if let Some(reason) = reason {
                writeln!(process().stdout(), "{} ({})", toolchain.name(), reason)?;
            } else {
                writeln!(process().stdout(), "{} (default)", toolchain.name())?;
            }
        }
    }
    Ok(utils::ExitCode(0))
}

fn show_rustup_home(cfg: &Cfg) -> Result<utils::ExitCode> {
    writeln!(process().stdout(), "{}", cfg.rustup_dir.display())?;
    Ok(utils::ExitCode(0))
}

fn target_list(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;

    if m.is_present("installed") {
        common::list_installed_targets(&toolchain)
    } else {
        common::list_targets(&toolchain)
    }
}

fn target_add(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;
    // XXX: long term move this error to cli ? the normal .into doesn't work
    // because Result here is the wrong sort and expression type ascription
    // isn't a feature yet.
    // list_components *and* add_component would both be inappropriate for
    // custom toolchains.
    if toolchain.is_custom() {
        return Err(crate::Error(
            crate::ErrorKind::ComponentsUnsupported(toolchain.name().to_string()),
            error_chain::State::default(),
        )
        .into());
    }

    let mut targets: Vec<String> = m
        .values_of("target")
        .unwrap()
        .map(ToString::to_string)
        .collect();

    if targets.contains(&"all".to_string()) {
        if targets.len() != 1 {
            return Err(ErrorKind::TargetAllSpecifiedWithTargets(targets).into());
        }

        targets.clear();
        let distributable = DistributableToolchain::new(&toolchain)?;
        for component in distributable.list_components()? {
            if component.component.short_name_in_manifest() == "rust-std"
                && component.available
                && !component.installed
            {
                let target = component
                    .component
                    .target
                    .as_ref()
                    .expect("rust-std should have a target");
                targets.push(target.to_string());
            }
        }
    }

    for target in &targets {
        let new_component = Component::new(
            "rust-std".to_string(),
            Some(TargetTriple::new(target)),
            false,
        );
        let distributable = DistributableToolchain::new(&toolchain)?;
        distributable.add_component(new_component)?;
    }

    Ok(utils::ExitCode(0))
}

fn target_remove(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;

    for target in m.values_of("target").unwrap() {
        let new_component = Component::new(
            "rust-std".to_string(),
            Some(TargetTriple::new(target)),
            false,
        );
        let distributable = DistributableToolchain::new(&toolchain)
            .chain_err(|| crate::ErrorKind::ComponentsUnsupported(toolchain.name().to_string()))?;
        distributable.remove_component(new_component)?;
    }

    Ok(utils::ExitCode(0))
}

fn component_list(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;

    if m.is_present("installed") {
        common::list_installed_components(&toolchain)
    } else {
        common::list_components(&toolchain)
    }
}

fn component_add(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;
    let distributable = DistributableToolchain::new(&toolchain)?;
    let target = m.value_of("target").map(TargetTriple::new).or_else(|| {
        distributable
            .desc()
            .as_ref()
            .ok()
            .map(|desc| desc.target.clone())
    });

    for component in m.values_of("component").unwrap() {
        let new_component = Component::new_with_target(component, false)
            .unwrap_or_else(|| Component::new(component.to_string(), target.clone(), true));
        distributable.add_component(new_component)?;
    }

    Ok(utils::ExitCode(0))
}

fn component_remove(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;
    let distributable = DistributableToolchain::new(&toolchain)
        .chain_err(|| crate::ErrorKind::ComponentsUnsupported(toolchain.name().to_string()))?;
    let target = m.value_of("target").map(TargetTriple::new).or_else(|| {
        distributable
            .desc()
            .as_ref()
            .ok()
            .map(|desc| desc.target.clone())
    });

    for component in m.values_of("component").unwrap() {
        let new_component = Component::new_with_target(component, false)
            .unwrap_or_else(|| Component::new(component.to_string(), target.clone(), true));
        distributable.remove_component(new_component)?;
    }

    Ok(utils::ExitCode(0))
}

fn explicit_or_dir_toolchain<'a>(cfg: &'a Cfg, m: &ArgMatches<'_>) -> Result<Toolchain<'a>> {
    let toolchain = m.value_of("toolchain");
    if let Some(toolchain) = toolchain {
        let toolchain = cfg.get_toolchain(toolchain, false)?;
        return Ok(toolchain);
    }

    let cwd = utils::current_dir()?;
    let (toolchain, _) = cfg.toolchain_for_dir(&cwd)?;

    Ok(toolchain)
}

fn toolchain_list(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    common::list_toolchains(cfg, m.is_present("verbose"))
}

fn toolchain_link(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = m.value_of("toolchain").unwrap();
    let path = m.value_of("path").unwrap();
    let toolchain = cfg.get_toolchain(toolchain, true)?;

    if let Ok(custom) = CustomToolchain::new(&toolchain) {
        custom.install_from_dir(Path::new(path), true)?;
        Ok(utils::ExitCode(0))
    } else {
        Err(ErrorKind::InvalidCustomToolchainName(toolchain.name().to_string()).into())
    }
}

fn toolchain_remove(cfg: &mut Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    for toolchain in m.values_of("toolchain").unwrap() {
        let toolchain = cfg.get_toolchain(toolchain, false)?;
        toolchain.remove()?;
    }
    Ok(utils::ExitCode(0))
}

fn override_add(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = m.value_of("toolchain").unwrap();
    let toolchain = cfg.get_toolchain(toolchain, false)?;

    let status = if !toolchain.is_custom() {
        let distributable = DistributableToolchain::new(&toolchain)?;
        Some(distributable.install_from_dist_if_not_installed()?)
    } else if !toolchain.exists() {
        return Err(ErrorKind::ToolchainNotInstalled(toolchain.name().to_string()).into());
    } else {
        None
    };

    let path = if let Some(path) = m.value_of("path") {
        PathBuf::from(path)
    } else {
        utils::current_dir()?
    };
    toolchain.make_override(&path)?;

    if let Some(status) = status {
        writeln!(process().stdout(),)?;
        common::show_channel_update(cfg, toolchain.name(), Ok(status))?;
    }

    Ok(utils::ExitCode(0))
}

fn override_remove(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let paths = if m.is_present("nonexistent") {
        let list: Vec<_> = cfg.settings_file.with(|s| {
            Ok(s.overrides
                .iter()
                .filter_map(|(k, _)| {
                    if Path::new(k).is_dir() {
                        None
                    } else {
                        Some(k.clone())
                    }
                })
                .collect())
        })?;
        if list.is_empty() {
            info!("no nonexistent paths detected");
        }
        list
    } else if m.is_present("path") {
        vec![m.value_of("path").unwrap().to_string()]
    } else {
        vec![utils::current_dir()?.to_str().unwrap().to_string()]
    };

    for path in paths {
        if cfg
            .settings_file
            .with_mut(|s| Ok(s.remove_override(&Path::new(&path), cfg.notify_handler.as_ref())))?
        {
            info!("override toolchain for '{}' removed", path);
        } else {
            info!("no override toolchain for '{}'", path);
            if !m.is_present("path") && !m.is_present("nonexistent") {
                info!(
                    "you may use `--path <path>` option to remove override toolchain \
                     for a specific path"
                );
            }
        }
    }
    Ok(utils::ExitCode(0))
}

const DOCS_DATA: &[(&str, &str, &str,)] = &[
    // flags can be used to open specific documents, e.g. `rustup doc --nomicon`
    // tuple elements: document name used as flag, help message, document index path
    ("alloc", "The Rust core allocation and collections library", "alloc/index.html"),
    ("book", "The Rust Programming Language book", "book/index.html"),
    ("cargo", "The Cargo Book", "cargo/index.html"),
    ("core", "The Rust Core Library", "core/index.html"),
    ("edition-guide", "The Rust Edition Guide", "edition-guide/index.html"),
    ("nomicon", "The Dark Arts of Advanced and Unsafe Rust Programming", "nomicon/index.html"),
    ("proc_macro", "A support library for macro authors when defining new macros", "proc_macro/index.html"),
    ("reference", "The Rust Reference", "reference/index.html"),
    ("rust-by-example", "A collection of runnable examples that illustrate various Rust concepts and standard libraries", "rust-by-example/index.html"),
    ("rustc", "The compiler for the Rust programming language", "rustc/index.html"),
    ("rustdoc", "Generate documentation for Rust projects", "rustdoc/index.html"),
    ("std", "Standard library API documentation", "std/index.html"),
    ("test", "Support code for rustc's built in unit-test and micro-benchmarking framework", "test/index.html"),
    ("unstable-book", "The Unstable Book", "unstable-book/index.html"),
    ("embedded-book", "The Embedded Rust Book", "embedded-book/index.html"),
];

fn doc(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let toolchain = explicit_or_dir_toolchain(cfg, m)?;
    if let Ok(distributable) = DistributableToolchain::new(&toolchain) {
        let components = distributable.list_components()?;
        if let [_] = components
            .into_iter()
            .filter(|cstatus| {
                cstatus.component.short_name_in_manifest() == "rust-docs" && !cstatus.installed
            })
            .take(1)
            .collect::<Vec<ComponentStatus>>()
            .as_slice()
        {
            info!(
                "`rust-docs` not installed in toolchain `{}`",
                toolchain.name()
            );
            info!(
                "To install, try `rustup component add --toolchain {} rust-docs`",
                toolchain.name()
            );
            return Err("unable to view documentation which is not installed".into());
        }
    }
    let topical_path: PathBuf;

    let doc_url = if let Some(topic) = m.value_of("topic") {
        topical_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?;
        topical_path.to_str().unwrap()
    } else if let Some((_, _, path)) = DOCS_DATA.iter().find(|(name, _, _)| m.is_present(name)) {
        path
    } else {
        "index.html"
    };

    if m.is_present("path") {
        let doc_path = toolchain.doc_path(doc_url)?;
        writeln!(process().stdout(), "{}", doc_path.display())?;
        Ok(utils::ExitCode(0))
    } else {
        toolchain.open_docs(doc_url)?;
        Ok(utils::ExitCode(0))
    }
}

fn man(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let command = m.value_of("command").unwrap();

    let toolchain = explicit_or_dir_toolchain(cfg, m)?;
    let mut toolchain = toolchain.path().to_path_buf();
    toolchain.push("share");
    toolchain.push("man");
    utils::assert_is_directory(&toolchain)?;

    let mut manpaths = std::ffi::OsString::from(toolchain);
    manpaths.push(":"); // prepend to the default MANPATH list
    if let Some(path) = process().var_os("MANPATH") {
        manpaths.push(path);
    }
    Command::new("man")
        .env("MANPATH", manpaths)
        .arg(command)
        .status()
        .expect("failed to open man page");
    Ok(utils::ExitCode(0))
}

fn self_uninstall(m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    let no_prompt = m.is_present("no-prompt");

    self_update::uninstall(no_prompt)
}

fn set_default_host_triple(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    cfg.set_default_host_triple(m.value_of("host_triple").unwrap())?;
    Ok(utils::ExitCode(0))
}

fn set_profile(cfg: &mut Cfg, m: &ArgMatches<'_>) -> Result<utils::ExitCode> {
    cfg.set_profile(&m.value_of("profile-name").unwrap())?;
    Ok(utils::ExitCode(0))
}

fn show_profile(cfg: &Cfg) -> Result<utils::ExitCode> {
    writeln!(process().stdout(), "{}", cfg.get_profile()?)?;
    Ok(utils::ExitCode(0))
}

fn show_keys(cfg: &Cfg) -> Result<utils::ExitCode> {
    for key in cfg.get_pgp_keys() {
        for l in key.show_key()? {
            info!("{}", l);
        }
    }
    Ok(utils::ExitCode(0))
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CompletionCommand {
    Rustup,
    Cargo,
}

static COMPLETIONS: &[(&str, CompletionCommand)] = &[
    ("rustup", CompletionCommand::Rustup),
    ("cargo", CompletionCommand::Cargo),
];

impl CompletionCommand {
    fn variants() -> Vec<&'static str> {
        COMPLETIONS.iter().map(|&(s, _)| s).collect::<Vec<_>>()
    }
}

impl FromStr for CompletionCommand {
    type Err = String;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match COMPLETIONS
            .iter()
            .find(|&(val, _)| val.eq_ignore_ascii_case(s))
        {
            Some(&(_, cmd)) => Ok(cmd),
            None => {
                let completion_options = COMPLETIONS
                    .iter()
                    .map(|&(v, _)| v)
                    .fold("".to_owned(), |s, v| format!("{}{}, ", s, v));
                Err(format!(
                    "[valid values: {}]",
                    completion_options.trim_end_matches(", ")
                ))
            }
        }
    }
}

impl fmt::Display for CompletionCommand {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match COMPLETIONS.iter().find(|&(_, cmd)| cmd == self) {
            Some(&(val, _)) => write!(f, "{}", val),
            None => unreachable!(),
        }
    }
}

fn output_completion_script(shell: Shell, command: CompletionCommand) -> Result<utils::ExitCode> {
    match command {
        CompletionCommand::Rustup => {
            cli().gen_completions_to("rustup", shell, &mut term2::stdout());
        }
        CompletionCommand::Cargo => {
            if let Shell::Zsh = shell {
                writeln!(&mut term2::stdout(), "#compdef cargo")?;
            }

            let script = match shell {
                Shell::Bash => "/etc/bash_completion.d/cargo",
                Shell::Zsh => "/share/zsh/site-functions/_cargo",
                _ => return Err(ErrorKind::UnsupportedCompletionShell(shell, command).into()),
            };

            writeln!(
                &mut term2::stdout(),
                "source $(rustc --print sysroot){}",
                script,
            )?;
        }
    }

    Ok(utils::ExitCode(0))
}


text:
from a10sdk.common.A10BaseClass import A10BaseClass

class EthList(A10BaseClass):

    """This class does not support CRUD Operations please use parent.

    :param eth_end: {"type": "number", "description": "Ethernet interface to sample", "format": "interface"}
    :param eth_start: {"type": "number", "description": "Ethernet interface to sample", "format": "interface"}
    :param DeviceProxy: The device proxy for REST operations and session handling. Refer to `common/device_proxy.py`

    """
    def __init__(self, **kwargs):
        self.ERROR_MSG = ""

        self.b_key = "eth-list"
        self.DeviceProxy = ""
        self.eth_end = ""
        self.eth_start = ""

        for keys, value in kwargs.items():
            setattr(self,keys, value)

class VeList(A10BaseClass):

    """This class does not support CRUD Operations please use parent.

    :param ve_end: {"$ref": "/axapi/v3/interface/ve", "type": "number", "description": "VE interface to sample", "format": "number"}
    :param ve_start: {"$ref": "/axapi/v3/interface/ve", "type": "number", "description": "VE interface to sample", "format": "number"}
    :param DeviceProxy: The device proxy for REST operations and session handling. Refer to `common/device_proxy.py`

    """
    def __init__(self, **kwargs):
        self.ERROR_MSG = ""

        self.b_key = "ve-list"
        self.DeviceProxy = ""
        self.ve_end = ""
        self.ve_start = ""

        for keys, value in kwargs.items():
            setattr(self,keys, value)

class Sampling(A10BaseClass):

    """    :param eth_list: {"minItems": 1, "items": {"type": "object"}, "uniqueItems": true, "type": "array", "array": [{"properties": {"eth-end": {"type": "number", "description": "Ethernet interface to sample", "format": "interface"}, "optional": true, "eth-start": {"type": "number", "description": "Ethernet interface to sample", "format": "interface"}}}]}
    :param uuid: {"description": "uuid of the object", "format": "string", "minLength": 1, "modify-not-allowed": 1, "optional": true, "maxLength": 64, "type": "string"}
    :param ve_list: {"minItems": 1, "items": {"type": "object"}, "uniqueItems": true, "type": "array", "array": [{"properties": {"ve-end": {"$ref": "/axapi/v3/interface/ve", "type": "number", "description": "VE interface to sample", "format": "number"}, "ve-start": {"$ref": "/axapi/v3/interface/ve", "type": "number", "description": "VE interface to sample", "format": "number"}, "optional": true}}]}
    :param DeviceProxy: The device proxy for REST operations and session handling. Refer to `common/device_proxy.py`

Class Description::
    Configure sFlow sampling on specified interfaces.

    Class sampling supports CRUD Operations and inherits from `common/A10BaseClass`.
    This class is the `"PARENT"` class for this module.`

    URL for this object::
    `https://<Hostname|Ip address>//axapi/v3/sflow/sampling`.

    """
    def __init__(self, **kwargs):
        self.ERROR_MSG = ""
        self.required=[]
        self.b_key = "sampling"
        self.a10_url="/axapi/v3/sflow/sampling"
        self.DeviceProxy = ""
        self.eth_list = []
        self.uuid = ""
        self.ve_list = []

        for keys, value in kwargs.items():
            setattr(self,keys, value)

redpajama_set_name: RedPajamaGithub


text:
var Promise = require('bluebird');
var redis = Promise.promisifyAll(require('redis'));

var JARC = module.exports = function JARedisConnector(keyDb, viewDb) {
  this.keyHost = keyDb[0];
  this.keyPort = keyDb[1];
  this.keyPw = keyDb[2];

  this.viewHost = viewDb[0];
  this.viewPort = viewDb[1];
  this.viewPw = viewDb[2];

  this.keyClient = redis.createClient(this.keyPort, this.keyHost, {
    'auth_pass': this.keyPw
  });
  this.viewClient = redis.createClient(this.viewPort, this.viewHost, {
    'auth_pass': this.viewPw
  });
};

JARC.prototype.generateKey = function (key) {
  var bucket = (key.split('').reduce(function (pv, cv) {
    return pv + cv.charCodeAt(0);
  }, 0) * 2000) / 1000;

  return ['viewKey:' + bucket, key];
};

JARC.prototype.getKey = function (key) {
  return this.keyClient.hgetAsync(key[0], key[1]);
};

JARC.prototype.setKey = function (key, value) {
  return this.keyClient.hsetAsync(key[0], key[1], value);
};

JARC.prototype.getView = function (viewKey) {
  return this.viewClient.getAsync(viewKey);
};

JARC.prototype.setView = function (viewKey, view) {
  return this.viewClient.setAsync(viewKey, view);
};


text:
"""Error types.

Note that some of these types may not currently be used -- they serve as
placeholders for error conditions in the future.
"""

class BldError(Exception):
    pass

class DuplicateUnitError(BldError):
    pass

class InvalidOptionRuleError(BldError):
    def __init__(self, line_num, rule, message):
        self.line_num = line_num
        self.rule = rule
        self.message = message

class InvalidOptionFileError(BldError):
    def __init__(self, file_path, invalid_rule_error):
        self.file_path = file_path
        self.rule_error = invalid_rule_error

    def __str__(self):
        return 'Invalid option rule in %s at line %s (%s):\n "%s"' % (
            self.file_path,
            self.rule_error.line_num,
            self.rule_error.message,
            self.rule_error.rule,
        )

class CycleError(BldError):
    pass

class InvalidInstallTargetError(BldError):
    pass

class MissingFileError(BldError):
    pass

class InvalidUfidError(BldError):
    pass

class InvalidUplidError(BldError):
    pass

class UnsupportedPlatformError(BldError):
    pass

class InvalidConfigFileError(BldError):
    pass

# -----------------------------------------------------------------------------
# Copyright 2015 Bloomberg Finance L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------- END-OF-FILE -----------------------------------


text:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UW.NLP.LanguageModels;

namespace UW.NLP.LanguageModelValidator
{
    /// <summary>
    /// This program executes steps by step the questions in the assignment.
    /// 1. Analyzes the three corpus usin 80% training, 10% validaiton and 10% test with both linear interpolation and back-off.
    /// 2. Optimzes lambda based on the three corpus
    /// 3. Trains in one corpus, tests in a different one.
    /// </summary>
    public class Program
    {
        static void Main(string[] args)
        {
            //ArgumentParser.Parse(args);
            //if (ArgumentParser.ShowedUsage) return;

            string brownCorpus = @"C:\Users\azend\Documents\GitHubVisualStudio\UWNLP\Assignment1\LanguageModels.UnitTests\TestData\brown.txt";
            string gutenberg = @"C:\Users\azend\Documents\GitHubVisualStudio\UWNLP\Assignment1\LanguageModels.UnitTests\TestData\gutenberg.txt";
            string reuters = @"C:\Users\azend\Documents\GitHubVisualStudio\UWNLP\Assignment1\LanguageModels.UnitTests\TestData\reuters.txt";

            // Analyze with Linear interpolation
            Console.WriteLine("First analyzing lambas");
            LinearInterpolationModel linearModel = new LinearInterpolationModel();
            Analyze(brownCorpus, linearModel);
            linearModel = new LinearInterpolationModel();
            Analyze(gutenberg, linearModel);
            linearModel = new LinearInterpolationModel();
            Analyze(reuters, linearModel);

            // Analyze with back-off
            Console.WriteLine("Analyze back-off");
            ExampleBackOffModelWithDiscounting backOff = new ExampleBackOffModelWithDiscounting();
            Analyze(brownCorpus, backOff);
            backOff = new ExampleBackOffModelWithDiscounting();
            Analyze(gutenberg, backOff);
            backOff = new ExampleBackOffModelWithDiscounting();
            Analyze(reuters, backOff);

            // Optimze Lambda
            List<List<string>> splitBrownCorpus = SplitCorpus(brownCorpus, 80, 10, 10);
            Console.WriteLine("Running optimizer with validation set for Brown.", 1000);
            linearModel = new LinearInterpolationModel();
            List<DoubleCombination> optimumCombinations = Optimizer.GetOptimumCombination(1000, linearModel, splitBrownCorpus[1]);

            Console.WriteLine("These are the optimum combinations:");
            foreach (DoubleCombination combination in optimumCombinations)
            {
                Console.WriteLine(combination.ToString());
            }

            // Optimze Lambda
            List<List<string>> splitGutenbergsCorpus = SplitCorpus(gutenberg, 80, 10, 10);
            Console.WriteLine("Running optimizer with validation set for Gutenberg.", 1000);
            linearModel = new LinearInterpolationModel();
            optimumCombinations = Optimizer.GetOptimumCombination(1000, linearModel, splitGutenbergsCorpus[1]);

            Console.WriteLine("These are the optimum combinations:");
            foreach (DoubleCombination combination in optimumCombinations)
            {
                Console.WriteLine(combination.ToString());
            }

            // Optimze Lambda
            List<List<string>> splitReutersCorpus = SplitCorpus(reuters, 80, 10, 10);
            Console.WriteLine("Running optimizer with validation set for Reuters.", 1000);
            linearModel = new LinearInterpolationModel();
            optimumCombinations = Optimizer.GetOptimumCombination(1000, linearModel, splitReutersCorpus[1]);

            Console.WriteLine("These are the optimum combinations:");
            foreach (DoubleCombination combination in optimumCombinations)
            {
                Console.WriteLine(combination.ToString());
            }

            // Train and test in differnet corpus
            Console.WriteLine("Train in Reuters, test in Brown");
            linearModel = new LinearInterpolationModel();
            Analyze(reuters, brownCorpus, linearModel);
            IEnumerable<KeyValuePair<NGram, int>> commonTrigrams = linearModel.NGramCounter.NGramCountDictionaries[3].OrderByDescending(kvp => kvp.Value).Take(100);
            Console.WriteLine("------------ Common 100 Trigrams -------");
            foreach (KeyValuePair<NGram, int> kvp in commonTrigrams)
            {
                Console.WriteLine("{0}\t\t{1}", kvp.Key, kvp.Value);
            }
            Console.WriteLine();

            // Train and test in differnet corpus
            Console.WriteLine("Train in Brown, test in Gutenberg");
            linearModel = new LinearInterpolationModel();
            Analyze(brownCorpus, gutenberg, linearModel);
            commonTrigrams = linearModel.NGramCounter.NGramCountDictionaries[3].OrderByDescending(kvp => kvp.Value).Take(100);
            Console.WriteLine("------------ Common 100 Trigrams -------");
            foreach (KeyValuePair<NGram, int> kvp in commonTrigrams)
            {
                Console.WriteLine("{0}\t\t{1}", kvp.Key, kvp.Value);
            }
            Console.WriteLine();

            // Train and test in differnet corpus
            Console.WriteLine("Train in Gutenbergs, test in Reuters");
            linearModel = new LinearInterpolationModel();
            Analyze(gutenberg, reuters, linearModel);
            commonTrigrams = linearModel.NGramCounter.NGramCountDictionaries[3].OrderByDescending(kvp => kvp.Value).Take(100);
            Console.WriteLine("------------ Common 100 Trigrams -------");
            foreach (KeyValuePair<NGram, int> kvp in commonTrigrams)
            {
                Console.WriteLine("{0}\t\t{1}", kvp.Key, kvp.Value);
            }
            Console.WriteLine();
        }

        private static void Analyze(string corpusPath, LanguageModel lm)
        {
            Console.WriteLine("Corpus path: {0}", corpusPath);
            Console.WriteLine();

            Console.WriteLine("Splitting corpus.");
            List<List<string>> splitCorpus = SplitCorpus(corpusPath,80, 10, 10);
            Console.WriteLine("Splitted corpus as follow:");
            Console.WriteLine("Training: {0}", splitCorpus[0].Count);
            Console.WriteLine("Validate: {0}", splitCorpus[1].Count);
            Console.WriteLine("Test: {0}", splitCorpus[2].Count);

            Console.WriteLine("Training model.");
            lm.Train(splitCorpus[0]);

            Console.WriteLine("Calculate Perplextiy with validate set.");
            PerplexityCalculator perplexityCalculator = new PerplexityCalculator(lm);
            int unkWords = 0;
            double perplexity = perplexityCalculator.GetPerplexity(splitCorpus[1], out unkWords);
            Console.WriteLine("Found {0} unknown words:", unkWords);
            Console.WriteLine("Perplexity of validation is {0}", perplexity);

            perplexity = perplexityCalculator.GetPerplexity(splitCorpus[2], out unkWords);
            Console.WriteLine("Found {0} unknown words:", unkWords);
            Console.WriteLine("Perplexity of testing is {0}", perplexity);
        }

        private static void Analyze(string trainCorpusPath, string testCorpusPath, LanguageModel lm)
        {
            Console.WriteLine("Corpus path: {0}", trainCorpusPath);
            Console.WriteLine();

            Console.WriteLine("Splitting corpus.");
            List<List<string>> splitCorpus = SplitCorpus(trainCorpusPath, 100, 0, 0);
            Console.WriteLine("Splitted corpus as follow:");
            Console.WriteLine("Training: {0}", splitCorpus[0].Count);
            Console.WriteLine("Validate: {0}", splitCorpus[1].Count);
            Console.WriteLine("Test: {0}", splitCorpus[2].Count);

            Console.WriteLine("Training model.");
            lm.Train(splitCorpus[0]);

            List<List<string>> splitTestCorpus = SplitCorpus(testCorpusPath, 100, 0, 0);
            Console.WriteLine("Calculate Perplextiy with test corpus.");
            PerplexityCalculator perplexityCalculator = new PerplexityCalculator(lm);
            int unkWords = 0;
            double perplexity = perplexityCalculator.GetPerplexity(splitTestCorpus[0], out unkWords);
            Console.WriteLine("Found {0} unknown words:", unkWords);
            Console.WriteLine("Perplexity of testing is {0}", perplexity);
        }

        private static List<List<string>> SplitCorpus(string corpusPath, double trainingPercentage, double validatePercentage, double testPercentage)
        {
            if (trainingPercentage + validatePercentage + testPercentage != 100)
                throw new InvalidOperationException("Percentages must sum 100%");

            List<string> lines = new List<string>(File.ReadAllLines(corpusPath));
            int trainingLines = (int)Math.Round((trainingPercentage / 100) * lines.Count);
            int validateLines = (int)Math.Round((validatePercentage / 100) * lines.Count);
            int testLines = lines.Count - trainingLines - validateLines;

            List<List<string>> splittedCorpus = new List<List<string>>();
            splittedCorpus.Add(new List<string>());
            splittedCorpus.Add(new List<string>());
            splittedCorpus.Add(new List<string>());
            for (int i = 0; i < lines.Count; i++)
            {
                if (i < trainingLines)
                {
                    splittedCorpus[0].Add(lines[i]);
                }
                else if (i - trainingLines < validateLines)
                {
                    splittedCorpus[1].Add(lines[i]);
                }
                else
                {
                    splittedCorpus[2].Add(lines[i]);
                }
            }

            return splittedCorpus;
        }
    }
}


text:
describe Facts::Meego::Os::Release do
  describe '#call_the_resolver' do
    subject(:fact) { Facts::Meego::Os::Release.new }

    before do
      allow(Facter::Resolvers::ReleaseFromFirstLine).to receive(:resolve)
        .with(:release, { release_file: '/etc/meego-release' })
        .and_return(value)
    end

    context 'when version is retrieved from specific file' do
      let(:value) { '2.13.0' }
      let(:release) { { 'full' => '2.13.0', 'major' => '2', 'minor' => '13' } }

      it 'calls Facter::Resolvers::ReleaseFromFirstLine with version' do
        fact.call_the_resolver
        expect(Facter::Resolvers::ReleaseFromFirstLine).to have_received(:resolve)
          .with(:release, release_file: '/etc/meego-release')
      end

      it 'returns operating system name fact' do
        expect(fact.call_the_resolver).to be_an_instance_of(Array).and \
          contain_exactly(an_object_having_attributes(name: 'os.release', value: release),
                          an_object_having_attributes(name: 'operatingsystemmajrelease',
                                                      value: release['major'], type: :legacy),
                          an_object_having_attributes(name: 'operatingsystemrelease',
                                                      value: release['full'], type: :legacy))
      end
    end

    context 'when version is retrieved from os-release file' do
      let(:value) { nil }
      let(:os_release) { 'beowulf' }
      let(:release) { { 'full' => 'beowulf', 'major' => 'beowulf' } }

      before do
        allow(Facter::Resolvers::OsRelease).to receive(:resolve).with(:version_id).and_return(os_release)
      end

      it 'calls Facter::Resolvers::OsRelease with version' do
        fact.call_the_resolver
        expect(Facter::Resolvers::OsRelease).to have_received(:resolve).with(:version_id)
      end

      it 'returns operating system name fact' do
        expect(fact.call_the_resolver).to be_an_instance_of(Array).and \
          contain_exactly(an_object_having_attributes(name: 'os.release', value: release),
                          an_object_having_attributes(name: 'operatingsystemmajrelease',
                                                      value: release['major'], type: :legacy),
                          an_object_having_attributes(name: 'operatingsystemrelease',
                                                      value: release['full'], type: :legacy))
      end

      context 'when release can\'t be received' do
        let(:os_release) { nil }

        it 'returns operating system name fact' do
          expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \
            have_attributes(name: 'os.release', value: nil)
        end
      end
    end
  end
end


text:
This repository contains the docs for each of the Docker official images. See [docker-library/official-images](https://github.com/docker-library/official-images) for the configuration how the images are built. To see all of the official images go to the [hub](https://hub.docker.com/explore/).

All Markdown files here are run through [tianon's fork of `markdownfmt`](https://github.com/tianon/markdownfmt) (only forked to add some smaller-diff preference and minor DockerHub-compatibility changes), and verified as formatted correctly via Travis-CI.

[![Build Status](https://travis-ci.org/docker-library/docs.svg?branch=master)](https://travis-ci.org/docker-library/docs)

# How do I add a new image's docs

-	create a folder for my image: `mkdir myimage`
-	create a `README-short.txt` (required, 100 char max)
-	create a `content.md` (required)
-	create a `license.md` (required)
-	add a `logo.png` (recommended)
-	edit `update.sh` as needed (see below)

Optionally: (we run this periodically, especially before pushing updated descriptions)

-	run `./update.sh myimage` to generate `myimage/README.md`
-	run `./markdownfmt.sh -l myimage` to verify whether format of your markdown files is compliant to `tianon/markdownfmt`. In case you see any file names, markdownfmt detected some issues, which might result in a failed build during continuous integration.

# What are all these files?

## `update.sh`

This is the main script used to generate the `README.md` files for each image. The generated file is committed along with the files used to generate it (see below on what customizations are available). When a new image is added that is not under the `docker-library` namespace on GitHub, a new entry must be added to the `otherRepos` array in this script. Accepted arguments are which image(s) you want to update and no arguments to update all of them.

## `generate-repo-stub-readme.sh`

This is used to generate a simple `README.md` to put in the image's repo. Argument is the name of the image, like `golang` and it then outputs the readme to standard out.

## `push.pl`

This is used by us to push the actual content of the READMEs to the Docker Hub as special access is required to modify the Hub description contents.

## `.template-helpers/generate-dockerfile-links-partial.sh`

This script is used by `update.sh` to create the "Supported tags and respective `Dockerfile` links" section of each generated `README.md` from the information in the [official-images `library/` manifests](https://github.com/docker-library/official-images/tree/master/library).

## `.template-helpers/template.md` and `.template-helpers/user-feedback.md`

These files are the templates used in building the `<image name>/README.md` file, in combination with the individual image's files.

## folder `<image name>`

This is where all the partial and generated files for a given image reside, (ex: `golang/`).

## `<image name>/README.md`

This file is generated using `update.sh`.

## `<image name>/content.md`

This file contains the main content of your image's long description. The basic parts you should have are a "What Is" section and a "How To" section. See the doc on [Official Repos](https://docs.docker.com/docker-hub/official_repos/#a-long-description) for more information on long description. The issues and contribution section is generated by the script but can be overridden. The following is a basic layout:

```markdown
# What is XYZ?

// about what the contained software is

%%LOGO%%

# How to use this image

// descriptions and examples of common use cases for the image
// make use of subsections as necessary
```

## `<image name>/README-short.txt`

This is the short description for the docker hub, limited to 100 characters in a single line.

> Go (golang) is a general purpose, higher-level, imperative programming language.

## `<image name>/logo.png`

Logo for the contained software. Specifications can be found in the docs on [Official Repos](https://docs.docker.com/docker-hub/official_repos/#a-logo)

## `<image name>/license.md`

This file should contain a link to the license for the main software in the image. Here is an example for `golang`:

```markdown
View [license information](http://golang.org/LICENSE) for the software contained in this image.
```

## `<image name>/user-feedback.md`

This file is an optional override of the default `user-feedback.md` for those repositories with different issue and contributing policies.

## `<image name>/mailing-list.md`

This file is snippet that gets inserted into the user feedback section to provide and extra way to get help, like a mailing list. Here is an example from the Postgres image:

```markdown
on the [mailing list](http://www.postgresql.org/community/lists/subscribe/) or
```

# Issues and Contributing

If you would like to make a new Official Image, be sure to follow the [guidelines](https://docs.docker.com/docker-hub/official_repos/) and talk to partners@docker.com.

Feel free to make a pull request for fixes and improvements to current documentation. For questions or problems on this repo come talk to us via the `#docker-library` IRC channel on [Freenode](https://freenode.net) or open up an issue.


text:
using System;

namespace ConventionsFixture
{
  public class ExceptionWithoutExceptionSuffix : Exception
  {

  }
}


text:
import * as autoprefixer from 'autoprefixer';
import * as cssnano from 'cssnano';
import * as gulp from 'gulp';
import * as gulpLoadPlugins from 'gulp-load-plugins';
import * as merge from 'merge-stream';
import { join } from 'path';

import {
  APP_DEST,
  APP_SRC,
  BROWSER_LIST,
  CSS_DEST,
  CSS_PROD_BUNDLE,
  DEPENDENCIES,
  ENV,
  getPluginConfig,
  TMP_DIR,
  ENABLE_SCSS
} from '../../config';

const plugins = <any>gulpLoadPlugins();
const cleanCss = require('gulp-clean-css');

const processors = [
  autoprefixer({
    browsers: BROWSER_LIST
  })
];

const isProd = ENV === 'prod';

if (isProd) {
  processors.push(
    cssnano({
      discardComments: {removeAll: true},
      discardUnused: false, // unsafe, see http://goo.gl/RtrzwF
      zindex: false, // unsafe, see http://goo.gl/vZ4gbQ
      reduceIdents: false // unsafe, see http://goo.gl/tNOPv0
    })
  );
}

/**
 * Copies all HTML files in `src/client` over to the `dist/tmp` directory.
 */
function prepareTemplates() {
  return gulp.src(join(APP_SRC, '**', '*.html'))
    .pipe(gulp.dest(TMP_DIR));
}

/**
 * Execute the appropriate component-stylesheet processing method based on presence of --scss flag.
 */
function processComponentStylesheets() {
  return ENABLE_SCSS ? processScss('component') : processComponentCss();
}

/**
 * Processes the SCSS files for the specified origin.
 * @param {string} origin The origin of the SCSS files being handled. Must be either 'component' or 'external.'
 */
function processScss(origin: 'component'|'external') {
  let scssSrc = getScssForOrigin(origin);
  let scssDest = getScssDestForOrigin(origin);
  return gulp.src(scssSrc)
    .pipe(isProd ? plugins.cached(`process-${origin}-scss`) : plugins.util.noop())
    .pipe(isProd ? plugins.progeny() : plugins.util.noop())
    .pipe(plugins.sourcemaps.init())
    .pipe(plugins.sass({includePaths: ['./node_modules/']}).on('error', plugins.sass.logError))
    .pipe(plugins.postcss(processors))
    .pipe(plugins.sourcemaps.write(isProd ? '.' : ''))
    .pipe(gulp.dest(scssDest));
}

/**
 * Get an array of SCSS files belonging to the specified origin.
 * @param {string} origin The origin of the SCSS files being handled. Must be either 'component' or 'external.'
 */
function getScssForOrigin(origin: 'component'|'external') {
  if (origin === 'component') return [join(APP_SRC, '**', '*.scss')]; else {
    return getExternalStylesheets().map(r => r.src);
  }
}

function getExternalStylesheets() {
  let stylesheet = ENABLE_SCSS ? 'scss' : 'css';
  return DEPENDENCIES.filter(d => new RegExp(`\.${stylesheet}$`).test(d.src));
}

/**
 * Get the destination of the processed SCSS files belonging to the specified origin.
 * @param {string} origin The origin of the SCSS files being handled. Must be either 'component' or 'external.'
 */
function getScssDestForOrigin(origin: 'component'|'external') {
  if (origin === 'component') return (isProd ? TMP_DIR : APP_DEST); else {
    return CSS_DEST;
  }
}

/**
 * Processes the CSS files within `src/client` excluding those in `src/client/assets` using `postcss` with the
 * configured processors.
 */
function processComponentCss() {
  return gulp.src([
      join(APP_SRC, '**', '*.css'),
      '!' + join(APP_SRC, 'assets', '**', '*.css')
    ])
    .pipe(isProd ? plugins.cached('process-component-css') : plugins.util.noop())
    .pipe(plugins.postcss(processors))
    .pipe(gulp.dest(isProd ? TMP_DIR : APP_DEST));
}

/**
 * Execute external-stylesheet processing method based on presence of --scss flag.
 */
function processExternalStylesheets() {
  return ENABLE_SCSS ? processScss('external') : processExternalCss();
}

/**
 * Processes the external CSS files using `postcss` with the configured processors.
 */
function processExternalCss() {
  return gulp.src(getExternalCss().map(r => r.src))
    .pipe(isProd ? plugins.cached('process-external-css') : plugins.util.noop())
    .pipe(plugins.postcss(processors))
    .pipe(isProd ? plugins.concatCss(CSS_PROD_BUNDLE, getPluginConfig('gulp-concat-css')) : plugins.util.noop())
    .pipe(isProd ? cleanCss() : plugins.util.noop())
    .pipe(gulp.dest(CSS_DEST));
}

/**
 * Returns the array of external CSS dependencies listed in the application configuration.
 */
function getExternalCss() {
  return DEPENDENCIES.filter(d => /\.css$/.test(d.src));
}

/**
 * Executes the build process, processing the HTML and CSS files.
 */
export = () => merge(processComponentStylesheets(), prepareTemplates(), processExternalStylesheets());