<commit_before>#![allow(clippy::large_enum_variant)]
#![allow(deprecated)] // because of `Error::description` deprecation in `error_chain`

use crate::dist::dist::Profile;
use crate::dist::manifest::{Component, Manifest};
use crate::dist::temp;
use crate::{component_for_bin, Toolchain};
use error_chain::error_chain;
use std::ffi::OsString;
use std::io::{self, Write};
use std::path::PathBuf;
use url::Url;

pub const TOOLSTATE_MSG: &str =
    "If you require these components, please install and use the latest successful build version,\n\
     which you can find at <https://rust-lang.github.io/rustup-components-history>.\n\nAfter determining \
     the correct date, install it with a command such as:\n\n    \
     rustup toolchain install nightly-2018-12-27\n\n\
     Then you can use the toolchain with commands such as:\n\n    \
     cargo +nightly-2018-12-27 build";

error_chain! {
    links {
        Download(download::Error, download::ErrorKind);
    }

    foreign_links {
        Temp(temp::Error);
        Io(io::Error);
        Open(opener::OpenError);
        Thread(std::sync::mpsc::RecvError);
    }

    errors {
        LocatingWorkingDir {
            description("Unable to proceed. Could not locate working directory.")
        }
        ReadingFile {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not read file")
            display("could not read {} file: '{}'", name, path.display())
        }
        ReadingDirectory {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not read directory")
            display("could not read {} directory: '{}'", name, path.display())
        }
        WritingFile {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not write file")
            display("could not write {} file: '{}'", name, path.display())
        }
        CreatingDirectory {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not create directory")
            display("could not create {} directory: '{}'", name, path.display())
        }
        ExpectedType(t: &'static str, n: String) {
            description("expected type")
            display("expected type: '{}' for '{}'", t, n)
        }
        FilteringFile {
            name: &'static str,
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not copy file")
            display("could not copy {} file from '{}' to '{}'", name, src.display(), dest.display())
        }
        RenamingFile {
            name: &'static str,
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not rename file")
            display("could not rename {} file from '{}' to '{}'",
                name, src.display(), dest.display())
        }
        RenamingDirectory {
            name: &'static str,
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not rename directory")
            display("could not rename {} directory from '{}' to '{}'", name, src.display(), dest.display())
        }
        DownloadingFile {
            url: Url,
            path: PathBuf,
        } {
            description("could not download file")
            display("could not download file from '{}' to '{}'", url, path.display())
        }
        DownloadNotExists {
            url: Url,
            path: PathBuf,
        } {
            description("could not download file")
            display("could not download file from '{}' to '{}'", url, path.display())
        }
        InvalidUrl {
            url: String,
        } {
            description("invalid url")
            display("invalid url: {}", url)
        }
        RunningCommand {
            name: OsString,
        } {
            description("command failed")
            display("command failed: '{}'", PathBuf::from(name).display())
        }
        NotAFile {
            path: PathBuf,
        } {
            description("not a file")
            display("not a file: '{}'", path.display())
        }
        NotADirectory {
            path: PathBuf,
        } {
            description("not a directory")
            display("not a directory: '{}'", path.display())
        }
        LinkingFile {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not link file")
            display("could not create link from '{}' to '{}'", src.display(), dest.display())
        }
        LinkingDirectory {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not symlink directory")
            display("could not create link from '{}' to '{}'", src.display(), dest.display())
        }
        CopyingDirectory {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not copy directory")
            display("could not copy directory from '{}' to '{}'", src.display(), dest.display())
        }
        CopyingFile {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not copy file")
            display("could not copy file from '{}' to '{}'", src.display(), dest.display())
        }
        RemovingFile {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not remove file")
            display("could not remove '{}' file: '{}'", name, path.display())
        }
        RemovingDirectory {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not remove directory")
            display("could not remove '{}' directory: '{}'", name, path.display())
        }
        SettingPermissions {
            path: PathBuf,
        } {
            description("failed to set permissions")
            display("failed to set permissions for '{}'", path.display())
        }
        CargoHome {
            description("couldn't find value of CARGO_HOME")
        }
        RustupHome {
            description("couldn't find value of RUSTUP_HOME")
        }
        InvalidToolchainName(t: String) {
            description("invalid toolchain name")
            display("invalid toolchain name: '{}'", t)
        }
        InvalidProfile(t: String) {
            description("invalid profile name")
            display("invalid profile name: '{}'; valid names are: {}", t, valid_profile_names())
        }
        ChecksumFailed {
            url: String,
            expected: String,
            calculated: String,
        } {
            description("checksum failed")
            display("checksum failed, expected: '{}', calculated: '{}'",
                    expected,
                    calculated)
        }
        SignatureVerificationInternalError(msg: String) {
            description("internal error verifying signature")
            display("internal error verifying signature: {}", msg)
        }
        SignatureVerificationFailed {
            url: String,
        } {
            description("signature verification failed")
            display("signature verification failed for {}", url)
        }
        ComponentConflict {
            name: String,
            path: PathBuf,
        } {
            description("conflicting component")
            display("failed to install component: '{}', detected conflict: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingFile {
            name: String,
            path: PathBuf,
        } {
            description("missing file in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingDir {
            name: String,
            path: PathBuf,
        } {
            description("missing directory in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        CorruptComponent(name: String) {
            description("corrupt component manifest")
            display("component manifest for '{}' is corrupt", name)
        }
        ExtractingPackage {
            description("failed to extract package (perhaps you ran out of disk space?)")
        }
        BadInstallerVersion(v: String) {
            description("unsupported installer version")
            display("unsupported installer version: {}", v)
        }
        BadInstalledMetadataVersion(v: String) {
            description("unsupported metadata version in existing installation")
            display("unsupported metadata version in existing installation: {}", v)
        }
        ComponentDirPermissionsFailed {
            description("I/O error walking directory during install")
        }
        ComponentFilePermissionsFailed {
            description("error setting file permissions during install")
        }
        ComponentDownloadFailed(c: String) {
            description("component download failed")
            display("component download failed for {}", c)
        }
        Parsing(e: toml::de::Error) {
            description("error parsing manifest")
        }
        UnsupportedVersion(v: String) {
            description("unsupported manifest version")
            display("manifest version '{}' is not supported", v)
        }
        MissingPackageForComponent(name: String) {
            description("missing package for component")
            display("server sent a broken manifest: missing package for component {}", name)
        }
        MissingPackageForRename(name: String) {
            description("missing package for the target of a rename")
            display("server sent a broken manifest: missing package for the target of a rename {}", name)
        }
        MissingReleaseForToolchain(name: String) {
            description("missing release for a toolchain")
            display("no release found for '{}'", name)
        }
        RequestedComponentsUnavailable(c: Vec<Component>, manifest: Manifest, toolchain: String) {
            description("some requested components are unavailable to download")
            display("{}", component_unavailable_msg(&c, &manifest, &toolchain))
        }
        UnknownMetadataVersion(v: String) {
            description("unknown metadata version")
            display("unknown metadata version: '{}'", v)
        }
        ToolchainNotInstalled(t: String) {
            description("toolchain is not installed")
            display("toolchain '{}' is not installed", t)
        }
        ToolchainNotSelected {
            description("toolchain is not selected")
            display("no override and no default toolchain set")
        }
        OverrideToolchainNotInstalled(t: String) {
            description("override toolchain is not installed")
            display("override toolchain '{}' is not installed", t)
        }
        BinaryNotFound(bin: String, t: String, is_default: bool) {
            description("toolchain does not contain binary")
            display("'{}' is not installed for the toolchain '{}'{}", bin, t, install_msg(bin, t, *is_default))
        }
        BinaryProvidedByUnavailableComponent(component: String, bin: String, toolchain: String) {
            description("binary is provided by a component which is not available in current toolchain")
            display("the '{}' component which provides the command '{}' is not available for the '{}' toolchain", component, bin, toolchain)
        }
        BinaryNotProvidedByComponent(component: String, bin: String, toolchain: String) {
            description("binary should be provided by component but isn't in current toolchain")
            display("the '{}' binary, normally provided by the '{}' component, is not applicable to the '{}' toolchain", bin, component, toolchain)
        }
        NeedMetadataUpgrade {
            description("rustup's metadata is out of date. run `rustup self upgrade-data`")
        }
        UpgradeIoError {
            description("I/O error during upgrade")
        }
        BadInstallerType(s: String) {
            description("invalid extension for installer")
            display("invalid extension for installer: '{}'", s)
        }
        ComponentsUnsupported(t: String) {
            description("toolchain does not support components")
            display("toolchain '{}' does not support components", t)
        }
        UnknownComponent(t: String, c: String, s: Option<String>) {
            description("toolchain does not contain component")
            display("toolchain '{}' does not contain component {}{}", t, c, if let Some(suggestion) = s {
                format!("; did you mean '{}'?", suggestion)
            } else {
                "".to_string()
            })
        }
        UnknownProfile(p: String) {
            description("unknown profile name")
            display(
                "unknown profile name: '{}'; valid profile names are {}",
                p,
                valid_profile_names(),
            )
        }
        AddingRequiredComponent(t: String, c: String) {
            description("required component cannot be added")
            display("component {} was automatically added because it is required for toolchain '{}'",
                    c, t)
        }
        ParsingFallbackSettings(e: toml::de::Error) {
            description("error parsing settings")
        }
        ParsingSettings(e: toml::de::Error) {
            description("error parsing settings")
        }
        NoExeName {
            description("couldn't determine self executable name")
        }
        UnsupportedKind(v: String) {
            description("unsupported tar entry")
            display("tar entry kind '{}' is not supported", v)
        }
        BadPath(v: PathBuf) {
            description("bad path in tar")
            display("tar path '{}' is not supported", v.display())
        }
        InvalidPgpKey(v: PathBuf, error: pgp::errors::Error) {
            description("invalid PGP key"),
            display("unable to read the PGP key '{}'", v.display())
        }
        BrokenPartialFile {
            description("partially downloaded file may have been damaged and was removed, please try again")
        }
        EmptyOverrideFile {
            description("empty toolchain override file detected. Please remove it, or else specify the desired toolchain properties in the file")
        }
        InvalidOverrideFile {
            description("missing toolchain properties in toolchain override file")
        }
        ParsingOverrideFile(e: toml::de::Error) {
            description("error parsing override file")
        }
    }
}

fn valid_profile_names() -> String {
    Profile::names()
        .iter()
        .map(|s| format!("'{}'", s))
        .collect::<Vec<_>>()
        .join(", ")
}

fn remove_component_msg(cs: &Component, manifest: &Manifest, toolchain: &str) -> String {
    if cs.short_name_in_manifest() == "rust-std" {
        // We special-case rust-std as it's the stdlib so really you want to do
        // rustup target remove
        format!(
            "    rustup target remove --toolchain {} {}",
            toolchain,
            cs.target.as_deref().unwrap_or(toolchain)
        )
    } else {
        format!(
            "    rustup component remove --toolchain {}{} {}",
            toolchain,
            if let Some(target) = cs.target.as_ref() {
                format!(" --target {}", target)
            } else {
                String::default()
            },
            cs.short_name(manifest)
        )
    }
}

fn component_unavailable_msg(cs: &[Component], manifest: &Manifest, toolchain: &str) -> String {
    assert!(!cs.is_empty());

    let mut buf = vec![];

    if cs.len() == 1 {
        let _ = writeln!(
            buf,
            "component {} is unavailable for download for channel '{}'",
            &cs[0].description(manifest),
            toolchain,
        );
        if toolchain.starts_with("nightly") {
            let _ = write!(
                buf,
                "Sometimes not all components are available in any given nightly. "
            );
        }
        let _ = write!(
            buf,
            "If you don't need the component, you can remove it with:\n\n{}",
            remove_component_msg(&cs[0], manifest, toolchain)
        );
    } else {
        // More than one component

        let same_target = cs
            .iter()
            .all(|c| c.target == cs[0].target || c.target.is_none());

        let cs_str = if same_target {
            cs.iter()
                .map(|c| format!("'{}'", c.short_name(manifest)))
                .collect::<Vec<_>>()
                .join(", ")
        } else {
            cs.iter()
                .map(|c| c.description(manifest))
                .collect::<Vec<_>>()
                .join(", ")
        };

        let remove_msg = cs
            .iter()
            .map(|c| remove_component_msg(c, manifest, toolchain))
            .collect::<Vec<_>>()
            .join("\n");
        let _ = write!(
            buf,
            "some components unavailable for download for channel '{}': {}\n\
            If you don't need the components, you can remove them with:\n\n{}\n\n{}",
            toolchain, cs_str, remove_msg, TOOLSTATE_MSG,
        );
    }

    String::from_utf8(buf).unwrap()
}

fn install_msg(bin: &str, toolchain: &str, is_default: bool) -> String {
    if Toolchain::is_custom_name(toolchain) {
        return "\nnote: this is a custom toolchain, which cannot use `rustup component add`\n\
        help: if you built this toolchain from source, and used `rustup toolchain link`, then you may be able to build the component with `x.py`".to_string();
    }
    match component_for_bin(bin) {
        Some(c) => format!("\nTo install, run `rustup component add {}{}`", c, {
            if is_default {
                String::new()
            } else {
                format!(" --toolchain {}", toolchain)
            }
        }),
        None => String::new(),
    }
}
<commit_msg>Better error message when no toolchain is installed<commit_after>#![allow(clippy::large_enum_variant)]
#![allow(deprecated)] // because of `Error::description` deprecation in `error_chain`

use crate::dist::dist::Profile;
use crate::dist::manifest::{Component, Manifest};
use crate::dist::temp;
use crate::{component_for_bin, Toolchain};
use error_chain::error_chain;
use std::ffi::OsString;
use std::io::{self, Write};
use std::path::PathBuf;
use url::Url;

pub const TOOLSTATE_MSG: &str =
    "If you require these components, please install and use the latest successful build version,\n\
     which you can find at <https://rust-lang.github.io/rustup-components-history>.\n\nAfter determining \
     the correct date, install it with a command such as:\n\n    \
     rustup toolchain install nightly-2018-12-27\n\n\
     Then you can use the toolchain with commands such as:\n\n    \
     cargo +nightly-2018-12-27 build";

error_chain! {
    links {
        Download(download::Error, download::ErrorKind);
    }

    foreign_links {
        Temp(temp::Error);
        Io(io::Error);
        Open(opener::OpenError);
        Thread(std::sync::mpsc::RecvError);
    }

    errors {
        LocatingWorkingDir {
            description("Unable to proceed. Could not locate working directory.")
        }
        ReadingFile {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not read file")
            display("could not read {} file: '{}'", name, path.display())
        }
        ReadingDirectory {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not read directory")
            display("could not read {} directory: '{}'", name, path.display())
        }
        WritingFile {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not write file")
            display("could not write {} file: '{}'", name, path.display())
        }
        CreatingDirectory {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not create directory")
            display("could not create {} directory: '{}'", name, path.display())
        }
        ExpectedType(t: &'static str, n: String) {
            description("expected type")
            display("expected type: '{}' for '{}'", t, n)
        }
        FilteringFile {
            name: &'static str,
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not copy file")
            display("could not copy {} file from '{}' to '{}'", name, src.display(), dest.display())
        }
        RenamingFile {
            name: &'static str,
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not rename file")
            display("could not rename {} file from '{}' to '{}'",
                name, src.display(), dest.display())
        }
        RenamingDirectory {
            name: &'static str,
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not rename directory")
            display("could not rename {} directory from '{}' to '{}'", name, src.display(), dest.display())
        }
        DownloadingFile {
            url: Url,
            path: PathBuf,
        } {
            description("could not download file")
            display("could not download file from '{}' to '{}'", url, path.display())
        }
        DownloadNotExists {
            url: Url,
            path: PathBuf,
        } {
            description("could not download file")
            display("could not download file from '{}' to '{}'", url, path.display())
        }
        InvalidUrl {
            url: String,
        } {
            description("invalid url")
            display("invalid url: {}", url)
        }
        RunningCommand {
            name: OsString,
        } {
            description("command failed")
            display("command failed: '{}'", PathBuf::from(name).display())
        }
        NotAFile {
            path: PathBuf,
        } {
            description("not a file")
            display("not a file: '{}'", path.display())
        }
        NotADirectory {
            path: PathBuf,
        } {
            description("not a directory")
            display("not a directory: '{}'", path.display())
        }
        LinkingFile {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not link file")
            display("could not create link from '{}' to '{}'", src.display(), dest.display())
        }
        LinkingDirectory {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not symlink directory")
            display("could not create link from '{}' to '{}'", src.display(), dest.display())
        }
        CopyingDirectory {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not copy directory")
            display("could not copy directory from '{}' to '{}'", src.display(), dest.display())
        }
        CopyingFile {
            src: PathBuf,
            dest: PathBuf,
        } {
            description("could not copy file")
            display("could not copy file from '{}' to '{}'", src.display(), dest.display())
        }
        RemovingFile {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not remove file")
            display("could not remove '{}' file: '{}'", name, path.display())
        }
        RemovingDirectory {
            name: &'static str,
            path: PathBuf,
        } {
            description("could not remove directory")
            display("could not remove '{}' directory: '{}'", name, path.display())
        }
        SettingPermissions {
            path: PathBuf,
        } {
            description("failed to set permissions")
            display("failed to set permissions for '{}'", path.display())
        }
        CargoHome {
            description("couldn't find value of CARGO_HOME")
        }
        RustupHome {
            description("couldn't find value of RUSTUP_HOME")
        }
        InvalidToolchainName(t: String) {
            description("invalid toolchain name")
            display("invalid toolchain name: '{}'", t)
        }
        InvalidProfile(t: String) {
            description("invalid profile name")
            display("invalid profile name: '{}'; valid names are: {}", t, valid_profile_names())
        }
        ChecksumFailed {
            url: String,
            expected: String,
            calculated: String,
        } {
            description("checksum failed")
            display("checksum failed, expected: '{}', calculated: '{}'",
                    expected,
                    calculated)
        }
        SignatureVerificationInternalError(msg: String) {
            description("internal error verifying signature")
            display("internal error verifying signature: {}", msg)
        }
        SignatureVerificationFailed {
            url: String,
        } {
            description("signature verification failed")
            display("signature verification failed for {}", url)
        }
        ComponentConflict {
            name: String,
            path: PathBuf,
        } {
            description("conflicting component")
            display("failed to install component: '{}', detected conflict: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingFile {
            name: String,
            path: PathBuf,
        } {
            description("missing file in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingDir {
            name: String,
            path: PathBuf,
        } {
            description("missing directory in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        CorruptComponent(name: String) {
            description("corrupt component manifest")
            display("component manifest for '{}' is corrupt", name)
        }
        ExtractingPackage {
            description("failed to extract package (perhaps you ran out of disk space?)")
        }
        BadInstallerVersion(v: String) {
            description("unsupported installer version")
            display("unsupported installer version: {}", v)
        }
        BadInstalledMetadataVersion(v: String) {
            description("unsupported metadata version in existing installation")
            display("unsupported metadata version in existing installation: {}", v)
        }
        ComponentDirPermissionsFailed {
            description("I/O error walking directory during install")
        }
        ComponentFilePermissionsFailed {
            description("error setting file permissions during install")
        }
        ComponentDownloadFailed(c: String) {
            description("component download failed")
            display("component download failed for {}", c)
        }
        Parsing(e: toml::de::Error) {
            description("error parsing manifest")
        }
        UnsupportedVersion(v: String) {
            description("unsupported manifest version")
            display("manifest version '{}' is not supported", v)
        }
        MissingPackageForComponent(name: String) {
            description("missing package for component")
            display("server sent a broken manifest: missing package for component {}", name)
        }
        MissingPackageForRename(name: String) {
            description("missing package for the target of a rename")
            display("server sent a broken manifest: missing package for the target of a rename {}", name)
        }
        MissingReleaseForToolchain(name: String) {
            description("missing release for a toolchain")
            display("no release found for '{}'", name)
        }
        RequestedComponentsUnavailable(c: Vec<Component>, manifest: Manifest, toolchain: String) {
            description("some requested components are unavailable to download")
            display("{}", component_unavailable_msg(&c, &manifest, &toolchain))
        }
        UnknownMetadataVersion(v: String) {
            description("unknown metadata version")
            display("unknown metadata version: '{}'", v)
        }
        ToolchainNotInstalled(t: String) {
            description("toolchain is not installed")
            display("toolchain '{}' is not installed", t)
        }
        ToolchainNotSelected {
            description("toolchain is not selected")
            display("no override and no default toolchain set; run 'rustup default stable' to set the stable toolchain as default")
        }
        OverrideToolchainNotInstalled(t: String) {
            description("override toolchain is not installed")
            display("override toolchain '{}' is not installed", t)
        }
        BinaryNotFound(bin: String, t: String, is_default: bool) {
            description("toolchain does not contain binary")
            display("'{}' is not installed for the toolchain '{}'{}", bin, t, install_msg(bin, t, *is_default))
        }
        BinaryProvidedByUnavailableComponent(component: String, bin: String, toolchain: String) {
            description("binary is provided by a component which is not available in current toolchain")
            display("the '{}' component which provides the command '{}' is not available for the '{}' toolchain", component, bin, toolchain)
        }
        BinaryNotProvidedByComponent(component: String, bin: String, toolchain: String) {
            description("binary should be provided by component but isn't in current toolchain")
            display("the '{}' binary, normally provided by the '{}' component, is not applicable to the '{}' toolchain", bin, component, toolchain)
        }
        NeedMetadataUpgrade {
            description("rustup's metadata is out of date. run `rustup self upgrade-data`")
        }
        UpgradeIoError {
            description("I/O error during upgrade")
        }
        BadInstallerType(s: String) {
            description("invalid extension for installer")
            display("invalid extension for installer: '{}'", s)
        }
        ComponentsUnsupported(t: String) {
            description("toolchain does not support components")
            display("toolchain '{}' does not support components", t)
        }
        UnknownComponent(t: String, c: String, s: Option<String>) {
            description("toolchain does not contain component")
            display("toolchain '{}' does not contain component {}{}", t, c, if let Some(suggestion) = s {
                format!("; did you mean '{}'?", suggestion)
            } else {
                "".to_string()
            })
        }
        UnknownProfile(p: String) {
            description("unknown profile name")
            display(
                "unknown profile name: '{}'; valid profile names are {}",
                p,
                valid_profile_names(),
            )
        }
        AddingRequiredComponent(t: String, c: String) {
            description("required component cannot be added")
            display("component {} was automatically added because it is required for toolchain '{}'",
                    c, t)
        }
        ParsingFallbackSettings(e: toml::de::Error) {
            description("error parsing settings")
        }
        ParsingSettings(e: toml::de::Error) {
            description("error parsing settings")
        }
        NoExeName {
            description("couldn't determine self executable name")
        }
        UnsupportedKind(v: String) {
            description("unsupported tar entry")
            display("tar entry kind '{}' is not supported", v)
        }
        BadPath(v: PathBuf) {
            description("bad path in tar")
            display("tar path '{}' is not supported", v.display())
        }
        InvalidPgpKey(v: PathBuf, error: pgp::errors::Error) {
            description("invalid PGP key"),
            display("unable to read the PGP key '{}'", v.display())
        }
        BrokenPartialFile {
            description("partially downloaded file may have been damaged and was removed, please try again")
        }
        EmptyOverrideFile {
            description("empty toolchain override file detected. Please remove it, or else specify the desired toolchain properties in the file")
        }
        InvalidOverrideFile {
            description("missing toolchain properties in toolchain override file")
        }
        ParsingOverrideFile(e: toml::de::Error) {
            description("error parsing override file")
        }
    }
}

fn valid_profile_names() -> String {
    Profile::names()
        .iter()
        .map(|s| format!("'{}'", s))
        .collect::<Vec<_>>()
        .join(", ")
}

fn remove_component_msg(cs: &Component, manifest: &Manifest, toolchain: &str) -> String {
    if cs.short_name_in_manifest() == "rust-std" {
        // We special-case rust-std as it's the stdlib so really you want to do
        // rustup target remove
        format!(
            "    rustup target remove --toolchain {} {}",
            toolchain,
            cs.target.as_deref().unwrap_or(toolchain)
        )
    } else {
        format!(
            "    rustup component remove --toolchain {}{} {}",
            toolchain,
            if let Some(target) = cs.target.as_ref() {
                format!(" --target {}", target)
            } else {
                String::default()
            },
            cs.short_name(manifest)
        )
    }
}

fn component_unavailable_msg(cs: &[Component], manifest: &Manifest, toolchain: &str) -> String {
    assert!(!cs.is_empty());

    let mut buf = vec![];

    if cs.len() == 1 {
        let _ = writeln!(
            buf,
            "component {} is unavailable for download for channel '{}'",
            &cs[0].description(manifest),
            toolchain,
        );
        if toolchain.starts_with("nightly") {
            let _ = write!(
                buf,
                "Sometimes not all components are available in any given nightly. "
            );
        }
        let _ = write!(
            buf,
            "If you don't need the component, you can remove it with:\n\n{}",
            remove_component_msg(&cs[0], manifest, toolchain)
        );
    } else {
        // More than one component

        let same_target = cs
            .iter()
            .all(|c| c.target == cs[0].target || c.target.is_none());

        let cs_str = if same_target {
            cs.iter()
                .map(|c| format!("'{}'", c.short_name(manifest)))
                .collect::<Vec<_>>()
                .join(", ")
        } else {
            cs.iter()
                .map(|c| c.description(manifest))
                .collect::<Vec<_>>()
                .join(", ")
        };

        let remove_msg = cs
            .iter()
            .map(|c| remove_component_msg(c, manifest, toolchain))
            .collect::<Vec<_>>()
            .join("\n");
        let _ = write!(
            buf,
            "some components unavailable for download for channel '{}': {}\n\
            If you don't need the components, you can remove them with:\n\n{}\n\n{}",
            toolchain, cs_str, remove_msg, TOOLSTATE_MSG,
        );
    }

    String::from_utf8(buf).unwrap()
}

fn install_msg(bin: &str, toolchain: &str, is_default: bool) -> String {
    if Toolchain::is_custom_name(toolchain) {
        return "\nnote: this is a custom toolchain, which cannot use `rustup component add`\n\
        help: if you built this toolchain from source, and used `rustup toolchain link`, then you may be able to build the component with `x.py`".to_string();
    }
    match component_for_bin(bin) {
        Some(c) => format!("\nTo install, run `rustup component add {}{}`", c, {
            if is_default {
                String::new()
            } else {
                format!(" --toolchain {}", toolchain)
            }
        }),
        None => String::new(),
    }
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate fuse;
extern crate libc;
extern crate regex;
extern crate rustc_serialize;
extern crate time;

use chrono::*;
use libc::ENOENT;
use time::Timespec;
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr,
           ReplyDirectory};
use rustc_serialize::json;
use std::path::Path;

use gd;

#[allow(dead_code)]
enum Category {
    Internal,
    Connectors,
    Dataload,
    DataloadDownload,
    DataloadEventstore,
    DataloadMetadataStorage,
    DataloadProcesses,
    EventStores,
    Invitations,
    Ldm,
    Metadata,
    MetadataAnalyticDashboard,
    MetadataAttributes,
    MetadataColumns,
    MetadataDataLoadingColumns,
    MetadataDatasets,
    MetadataDateFilterSettings,
    MetadataDimensions,
    MetadataDomains,
    MetadataEtlFiles,
    MetadataExecutionContexts,
    MetadataFacts,
    MetadataFilters,
    MetadataFolders,
    MetadataKpi,
    MetadataKpiAlert,
    MetadataListAttributeFilter,
    MetadataMetrics,
    MetadataProjectDashboards,
    MetadataPrompts,
    MetadataReportDefinition,
    MetadataReports,
    MetadataSchedulEdmails,
    MetadataTableDataLoads,
    MetadataTables,
    MetadataUserFilters,
    MetadataVisualizations,
    PublicArtifacts,
    Roles,
    Schedules,
    Templates,
    Uploads,
    Users,
}

#[allow(dead_code)]
enum ReservedFile {
    FeatureFlagsJson = 2,
    PermissionsJson,
    ProjectJson,
    RolesJson,
}

const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; // 1 second

const CREATE_TIME: Timespec = Timespec {
    sec: 1381237736,
    nsec: 0,
};    // 2013-10-08 08:56

const INODE_ROOT: u64 = 1;
const INODE_USER: u64 = 2;
const INODE_PROJECTS: u64 = 3;
const INODE_PROJECTS_JSON: u64 = 4;

const ROOT_DIR_ATTR: FileAttr = FileAttr {
    ino: INODE_ROOT,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

const PROJECTS_DIR_ATTR: FileAttr = FileAttr {
    ino: INODE_PROJECTS,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

pub struct GoodDataFS {
    pub client: gd::GoodDataClient,
}

impl Drop for GoodDataFS {
    fn drop(&mut self) {
        println!("Unmounting GoodData Filesystem");
    }
}

#[allow(dead_code)]
impl GoodDataFS {
    pub fn drop(&mut self) {
        println!("NOTE: Logging out is not implemented yet!");
    }

    pub fn inode_get_project(inode: u64) -> u16 {
        (inode >> 48) as u16
    }

    pub fn inode_get_category(inode: u64) -> u8 {
        ((inode >> 40) & 0xff) as u8
    }

    pub fn inode_get_item(inode: u64) -> u32 {
        ((inode >> 8) & 0xffffffff) as u32
    }

    pub fn inode_get_reserved(inode: u64) -> u8 {
        (inode & 0xff) as u8
    }

    pub fn inode_create(project: u16, category: u8, item: u32, reserved: u8) -> u64 {
        let inode: u64 = ((project as u64) << 48) | ((category as u64) << 40) |
                         ((item as u64) << 8) | (reserved as u64);
        return inode;
    }

    fn client(&self) -> &gd::GoodDataClient {
        &self.client
    }

    fn get_project_dir_attributes(&self, inode: u64) -> fuse::FileAttr {
        println!("GoodDataFS::get_project_dir_attributes() inode {}", inode);
        FileAttr {
            ino: inode,
            size: 0,
            blocks: 0,
            atime: CREATE_TIME,
            mtime: CREATE_TIME,
            ctime: CREATE_TIME,
            crtime: CREATE_TIME,
            kind: FileType::Directory,
            perm: 0o755,
            nlink: 0,
            uid: 501,
            gid: 20,
            rdev: 0,
            flags: 0,
        }
    }

    fn get_user_file_attributes(&self) -> fuse::FileAttr {
        let json = format!("{}\n",
                           json::as_pretty_json(&self.client.user()).to_string());

        let user = json::decode::<gd::AccountSetting>(&json);

        let ts = UTC.datetime_from_str(&user.unwrap().accountSetting.updated.unwrap()[..],
                               "%Y-%m-%d %H:%M:%S")
            .unwrap()
            .timestamp();

        let updated = time::Timespec::new(ts, 0);

        FileAttr {
            ino: INODE_USER,
            size: json.len() as u64,
            blocks: 1,
            atime: updated,
            mtime: updated,
            ctime: updated,
            crtime: updated,
            kind: FileType::RegularFile,
            perm: 0o444,
            nlink: 1,
            uid: 501,
            gid: 20,
            rdev: 0,
            flags: 0,
        }
    }

    fn get_projects_file_attributes(&self) -> fuse::FileAttr {
        let json = format!("{}\n",
                           json::as_pretty_json(&self.client.projects()).to_string());

        FileAttr {
            ino: INODE_PROJECTS_JSON,
            size: json.len() as u64,
            blocks: 1,
            atime: CREATE_TIME,
            mtime: CREATE_TIME,
            ctime: CREATE_TIME,
            crtime: CREATE_TIME,
            kind: FileType::RegularFile,
            perm: 0o444,
            nlink: 1,
            uid: 501,
            gid: 20,
            rdev: 0,
            flags: 0,
        }
    }
}

impl Filesystem for GoodDataFS {
    fn lookup(&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
        println!("GoodDataFS::lookup() - Reading parent {}, path {:?}",
                 parent,
                 name.to_str().unwrap());
        if parent == INODE_ROOT && name.to_str() == Some("user.json") {
            reply.entry(&TTL, &self.get_user_file_attributes(), 0);
        } else if parent == INODE_ROOT && name.to_str() == Some("projects") {
            reply.entry(&TTL, &PROJECTS_DIR_ATTR, 0);
        } else if parent == INODE_PROJECTS && name.to_str() == Some("projects.json") {
            reply.entry(&TTL, &self.get_projects_file_attributes(), 0);
        } else if parent == INODE_PROJECTS {
            let mut i: u64 = 0;
            let client: &gd::GoodDataClient = self.client();
            let projects = client.projects().as_ref();
            for project in projects.unwrap().into_iter() {
                let title: &String = project.project()
                    .meta()
                    .title()
                    .as_ref()
                    .unwrap();

                if title == name.to_str().unwrap() {
                    break;
                }
                i += 1;
            }

            let inode = (i + 1) << 48;
            println!("GoodDataFS::lookup() - Adding path {:?}, inode {}",
                     name,
                     inode);
            let attr = FileAttr {
                ino: inode,
                size: 0,
                blocks: 0,
                atime: CREATE_TIME,
                mtime: CREATE_TIME,
                ctime: CREATE_TIME,
                crtime: CREATE_TIME,
                kind: FileType::Directory,
                perm: 0o755,
                nlink: 2,
                uid: 501,
                gid: 20,
                rdev: 0,
                flags: 0,
            };
            reply.entry(&TTL, &attr, 0);
        } else {
            let projectid = GoodDataFS::inode_get_project(parent);
            if projectid > 0 {
                println!("GoodDataFS::lookup() - Adding project projectid {}, path {:?}",
                         projectid - 1,
                         name.to_str());
                if name.to_str() == Some("project.json") {
                    let inode = GoodDataFS::inode_create(projectid, 0, 0, 1);

                    let client: &gd::GoodDataClient = self.client();
                    let projects = client.projects().as_ref();
                    let json = json::as_pretty_json(&projects.unwrap()[(projectid - 1) as usize])
                        .to_string();
                    let attr = FileAttr {
                        ino: inode,
                        size: json.len() as u64,
                        blocks: 1,
                        atime: CREATE_TIME,
                        mtime: CREATE_TIME,
                        ctime: CREATE_TIME,
                        crtime: CREATE_TIME,
                        kind: FileType::RegularFile,
                        perm: 0o444,
                        nlink: 1,
                        uid: 501,
                        gid: 20,
                        rdev: 0,
                        flags: 0,
                    };
                    reply.entry(&TTL, &attr, 0);
                } else {
                    reply.error(ENOENT);
                }
            } else {
                reply.error(ENOENT);
            }
        }
    }

    fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        println!("GoodDataFS::getattr() - Reading inode {}", ino);

        if ino == INODE_ROOT {
            reply.attr(&TTL, &ROOT_DIR_ATTR);
        } else if ino == INODE_PROJECTS {
            reply.attr(&TTL, &PROJECTS_DIR_ATTR);
        } else if ino == INODE_PROJECTS_JSON {
            reply.attr(&TTL, &self.get_projects_file_attributes());
        } else if ino == INODE_USER {
            reply.attr(&TTL, &self.get_user_file_attributes());
        } else {
            let projectid = ino >> 48;
            if projectid > 0 {
                println!("GoodDataFS::getattr() - Project Specific Info {:?}",
                         projectid - 1);
                reply.attr(&TTL, &self.get_project_dir_attributes(ino));
            } else {
                println!("GoodDataFS::getattr() - Not found inode {:?}", ino);
                reply.error(ENOENT);
            }
        }
    }

    fn read(&mut self,
            _req: &Request,
            ino: u64,
            _fh: u64,
            offset: u64,
            _size: u32,
            reply: ReplyData) {
        println!("GoodDataFS::read() - Reading inode {}", ino);
        if ino == INODE_USER {
            let json = format!("{}\n",
                               json::as_pretty_json(&self.client.user()).to_string());
            reply.data(&json.as_bytes()[offset as usize..]);
        } else if ino == INODE_PROJECTS_JSON {
            println!("GoodDataFS::read() - Reading projects.json");
            let json = format!("{}\n",
                               json::as_pretty_json(&self.client.projects()).to_string());
            reply.data(&json.as_bytes()[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir(&mut self,
               _req: &Request,
               ino: u64,
               _fh: u64,
               offset: u64,
               mut reply: ReplyDirectory) {
        println!("GoodDataFS::readdir() - Reading inode {}", ino);
        if ino == INODE_ROOT {
            if offset == 0 {
                // reply.add(INODE_ROOT, 0, FileType::Directory, ".");
                // reply.add(INODE_ROOT, 1, FileType::Directory, "..");

                reply.add(INODE_ROOT, 2, FileType::RegularFile, "user.json");
                reply.add(INODE_ROOT, 3, FileType::Directory, "projects");
            }
            reply.ok();
        } else if ino == INODE_PROJECTS {
            if offset == 0 {
                reply.add(INODE_PROJECTS, 0, FileType::Directory, ".");
                reply.add(INODE_PROJECTS, 1, FileType::Directory, "..");

                self.client.projects_fetch();

                let mut i: u64 = 0;
                let client: &gd::GoodDataClient = self.client();
                let projects = client.projects().as_ref();
                for project in projects.unwrap().into_iter() {
                    let title: &String = project.project()
                        .meta()
                        .title()
                        .as_ref()
                        .unwrap();

                    let inode = (i + 1) << 48;
                    println!("GoodDataFS::readdir() - Adding path {:?}, inode {}",
                             title,
                             inode);
                    // let sanitized = re.replace_all(&title[..], "_");
                    reply.add(INODE_PROJECTS, i + 2, FileType::Directory, title);
                    i += 1;
                }

                reply.add(INODE_PROJECTS_JSON,
                          i + 3,
                          FileType::RegularFile,
                          "projects.json");
            }
            reply.ok();
        } else {
            let projectid = ino >> 48;
            if projectid > 0 {
                if offset == 0 {
                    println!("GoodDataFS::readdir() - Reading project specific inode {}, \
                              projectid {:?}",
                             ino,
                             projectid - 1);

                    reply.add(ino, 0, FileType::Directory, ".");
                    reply.add(ino, 1, FileType::Directory, "..");

                    println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                              featureflags.json",
                             GoodDataFS::inode_create(projectid as u16,
                                                      0,
                                                      0,
                                                      ReservedFile::FeatureFlagsJson as u8),
                             projectid - 1);
                    reply.add(GoodDataFS::inode_create(projectid as u16,
                                                       0,
                                                       0,
                                                       ReservedFile::FeatureFlagsJson as u8),
                              2,
                              FileType::RegularFile,
                              "featureflags.json");

                    println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                              permissions.json",
                             GoodDataFS::inode_create(projectid as u16,
                                                      0,
                                                      0,
                                                      ReservedFile::PermissionsJson as u8),
                             projectid - 1);
                    reply.add(GoodDataFS::inode_create(projectid as u16,
                                                       0,
                                                       0,
                                                       ReservedFile::PermissionsJson as u8),
                              3,
                              FileType::RegularFile,
                              "permissions.json");

                    println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                              project.json",
                             GoodDataFS::inode_create(projectid as u16,
                                                      0,
                                                      0,
                                                      ReservedFile::ProjectJson as u8),
                             projectid - 1);
                    reply.add(GoodDataFS::inode_create(projectid as u16,
                                                       0,
                                                       0,
                                                       ReservedFile::ProjectJson as u8),
                              4,
                              FileType::RegularFile,
                              "project.json");

                    println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                              roles.json",
                             GoodDataFS::inode_create(projectid as u16,
                                                      0,
                                                      0,
                                                      ReservedFile::RolesJson as u8),
                             projectid - 1);
                    reply.add(GoodDataFS::inode_create(projectid as u16,
                                                       0,
                                                       0,
                                                       ReservedFile::RolesJson as u8),
                              5,
                              FileType::RegularFile,
                              "roles.json");
                }
                reply.ok();
            } else {
                println!("GoodDataFS::readdir() - Unknown inode {}", ino);
                reply.error(ENOENT);
            }
        }
    }
}

impl GoodDataFS {
    pub fn mount(self, mountpoint: String) {
        println!("Mounting GoodData as Filesystem, mountpoint: {}",
                 mountpoint);

        fuse::mount(self, &mountpoint, &[]);
    }
}
<commit_msg>Cosmetics<commit_after>extern crate chrono;
extern crate fuse;
extern crate libc;
extern crate regex;
extern crate rustc_serialize;
extern crate time;

use chrono::*;
use libc::ENOENT;
use time::Timespec;
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr,
           ReplyDirectory};
use rustc_serialize::json;
use std::path::Path;

use gd;

#[allow(dead_code)]
enum Category {
    Internal,
    Connectors,
    Dataload,
    DataloadDownload,
    DataloadEventstore,
    DataloadMetadataStorage,
    DataloadProcesses,
    EventStores,
    Invitations,
    Ldm,
    Metadata,
    MetadataAnalyticDashboard,
    MetadataAttributes,
    MetadataColumns,
    MetadataDataLoadingColumns,
    MetadataDatasets,
    MetadataDateFilterSettings,
    MetadataDimensions,
    MetadataDomains,
    MetadataEtlFiles,
    MetadataExecutionContexts,
    MetadataFacts,
    MetadataFilters,
    MetadataFolders,
    MetadataKpi,
    MetadataKpiAlert,
    MetadataListAttributeFilter,
    MetadataMetrics,
    MetadataProjectDashboards,
    MetadataPrompts,
    MetadataReportDefinition,
    MetadataReports,
    MetadataSchedulEdmails,
    MetadataTableDataLoads,
    MetadataTables,
    MetadataUserFilters,
    MetadataVisualizations,
    PublicArtifacts,
    Roles,
    Schedules,
    Templates,
    Uploads,
    Users,
}

#[allow(dead_code)]
enum ReservedFile {
    FeatureFlagsJson = 2,
    PermissionsJson,
    ProjectJson,
    RolesJson,
}

const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; // 1 second

const CREATE_TIME: Timespec = Timespec {
    sec: 1381237736,
    nsec: 0,
};    // 2013-10-08 08:56

const INODE_ROOT: u64 = 1;
const INODE_USER: u64 = 2;
const INODE_PROJECTS: u64 = 3;
const INODE_PROJECTS_JSON: u64 = 4;

const ROOT_DIR_ATTR: FileAttr = FileAttr {
    ino: INODE_ROOT,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

const PROJECTS_DIR_ATTR: FileAttr = FileAttr {
    ino: INODE_PROJECTS,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

pub struct GoodDataFS {
    pub client: gd::GoodDataClient,
}

impl Drop for GoodDataFS {
    fn drop(&mut self) {
        println!("Unmounting GoodData Filesystem");
    }
}

#[allow(dead_code)]
impl GoodDataFS {
    pub fn drop(&mut self) {
        println!("NOTE: Logging out is not implemented yet!");
    }

    pub fn inode_get_project(inode: u64) -> u16 {
        (inode >> 48) as u16
    }

    pub fn inode_get_category(inode: u64) -> u8 {
        ((inode >> 40) & 0xff) as u8
    }

    pub fn inode_get_item(inode: u64) -> u32 {
        ((inode >> 8) & 0xffffffff) as u32
    }

    pub fn inode_get_reserved(inode: u64) -> u8 {
        (inode & 0xff) as u8
    }

    pub fn inode_create(project: u16, category: u8, item: u32, reserved: u8) -> u64 {
        let inode: u64 = ((project as u64) << 48) | ((category as u64) << 40) |
                         ((item as u64) << 8) | (reserved as u64);
        return inode;
    }

    fn client(&self) -> &gd::GoodDataClient {
        &self.client
    }

    fn get_project_dir_attributes(&self, inode: u64) -> fuse::FileAttr {
        println!("GoodDataFS::get_project_dir_attributes() inode {}", inode);
        FileAttr {
            ino: inode,
            size: 0,
            blocks: 0,
            atime: CREATE_TIME,
            mtime: CREATE_TIME,
            ctime: CREATE_TIME,
            crtime: CREATE_TIME,
            kind: FileType::Directory,
            perm: 0o755,
            nlink: 0,
            uid: 501,
            gid: 20,
            rdev: 0,
            flags: 0,
        }
    }

    fn get_user_file_attributes(&self) -> fuse::FileAttr {
        let json = format!("{}\n",
                           json::as_pretty_json(&self.client.user()).to_string());

        let user = json::decode::<gd::AccountSetting>(&json);

        let ts = UTC.datetime_from_str(&user.unwrap().accountSetting.updated.unwrap()[..],
                               "%Y-%m-%d %H:%M:%S")
            .unwrap()
            .timestamp();

        let updated = time::Timespec::new(ts, 0);

        FileAttr {
            ino: INODE_USER,
            size: json.len() as u64,
            blocks: 1,
            atime: updated,
            mtime: updated,
            ctime: updated,
            crtime: updated,
            kind: FileType::RegularFile,
            perm: 0o444,
            nlink: 1,
            uid: 501,
            gid: 20,
            rdev: 0,
            flags: 0,
        }
    }

    fn get_projects_file_attributes(&self) -> fuse::FileAttr {
        let json = format!("{}\n",
                           json::as_pretty_json(&self.client.projects()).to_string());

        FileAttr {
            ino: INODE_PROJECTS_JSON,
            size: json.len() as u64,
            blocks: 1,
            atime: CREATE_TIME,
            mtime: CREATE_TIME,
            ctime: CREATE_TIME,
            crtime: CREATE_TIME,
            kind: FileType::RegularFile,
            perm: 0o444,
            nlink: 1,
            uid: 501,
            gid: 20,
            rdev: 0,
            flags: 0,
        }
    }

    pub fn readdir_project(&self, projectid: u16, reply: &mut ReplyDirectory) {
        let fileinode = GoodDataFS::inode_create(projectid as u16,
                                                 Category::Internal as u8,
                                                 0,
                                                 ReservedFile::FeatureFlagsJson as u8);
        println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                  featureflags.json",
                 fileinode,
                 projectid - 1);
        reply.add(fileinode, 2, FileType::RegularFile, "featureflags.json");

        let filenode = GoodDataFS::inode_create(projectid as u16,
                                                Category::Internal as u8,
                                                0,
                                                ReservedFile::PermissionsJson as u8);
        println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                  permissions.json",
                 filenode,
                 projectid - 1);
        reply.add(filenode, 3, FileType::RegularFile, "permissions.json");

        let filenode = GoodDataFS::inode_create(projectid as u16,
                                                Category::Internal as u8,
                                                0,
                                                ReservedFile::ProjectJson as u8);
        println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                  project.json",
                 filenode,
                 projectid - 1);
        reply.add(filenode, 4, FileType::RegularFile, "project.json");

        let filenode = GoodDataFS::inode_create(projectid as u16,
                                                Category::Internal as u8,
                                                0,
                                                ReservedFile::RolesJson as u8);
        println!("GoodDataFS::readdir() - Adding inode {}, project {}, path \
                  roles.json",
                 filenode,
                 projectid - 1);
        reply.add(filenode, 5, FileType::RegularFile, "roles.json");
    }
}

impl Filesystem for GoodDataFS {
    fn lookup(&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
        println!("GoodDataFS::lookup() - Reading parent {}, path {:?}",
                 parent,
                 name.to_str().unwrap());
        if parent == INODE_ROOT && name.to_str() == Some("user.json") {
            reply.entry(&TTL, &self.get_user_file_attributes(), 0);
        } else if parent == INODE_ROOT && name.to_str() == Some("projects") {
            reply.entry(&TTL, &PROJECTS_DIR_ATTR, 0);
        } else if parent == INODE_PROJECTS && name.to_str() == Some("projects.json") {
            reply.entry(&TTL, &self.get_projects_file_attributes(), 0);
        } else if parent == INODE_PROJECTS {
            let mut i: u64 = 0;
            let client: &gd::GoodDataClient = self.client();
            let projects = client.projects().as_ref();
            for project in projects.unwrap().into_iter() {
                let title: &String = project.project()
                    .meta()
                    .title()
                    .as_ref()
                    .unwrap();

                if title == name.to_str().unwrap() {
                    break;
                }
                i += 1;
            }

            let inode = (i + 1) << 48;
            println!("GoodDataFS::lookup() - Adding path {:?}, inode {}",
                     name,
                     inode);
            let attr = FileAttr {
                ino: inode,
                size: 0,
                blocks: 0,
                atime: CREATE_TIME,
                mtime: CREATE_TIME,
                ctime: CREATE_TIME,
                crtime: CREATE_TIME,
                kind: FileType::Directory,
                perm: 0o755,
                nlink: 2,
                uid: 501,
                gid: 20,
                rdev: 0,
                flags: 0,
            };
            reply.entry(&TTL, &attr, 0);
        } else {
            let projectid = GoodDataFS::inode_get_project(parent);
            if projectid > 0 {
                println!("GoodDataFS::lookup() - Adding project projectid {}, path {:?}",
                         projectid - 1,
                         name.to_str());
                if name.to_str() == Some("project.json") {
                    let inode = GoodDataFS::inode_create(projectid, 0, 0, 1);

                    let client: &gd::GoodDataClient = self.client();
                    let projects = client.projects().as_ref();
                    let json = json::as_pretty_json(&projects.unwrap()[(projectid - 1) as usize])
                        .to_string();
                    let attr = FileAttr {
                        ino: inode,
                        size: json.len() as u64,
                        blocks: 1,
                        atime: CREATE_TIME,
                        mtime: CREATE_TIME,
                        ctime: CREATE_TIME,
                        crtime: CREATE_TIME,
                        kind: FileType::RegularFile,
                        perm: 0o444,
                        nlink: 1,
                        uid: 501,
                        gid: 20,
                        rdev: 0,
                        flags: 0,
                    };
                    reply.entry(&TTL, &attr, 0);
                } else {
                    reply.error(ENOENT);
                }
            } else {
                reply.error(ENOENT);
            }
        }
    }

    fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        println!("GoodDataFS::getattr() - Reading inode {}", ino);

        if ino == INODE_ROOT {
            reply.attr(&TTL, &ROOT_DIR_ATTR);
        } else if ino == INODE_PROJECTS {
            reply.attr(&TTL, &PROJECTS_DIR_ATTR);
        } else if ino == INODE_PROJECTS_JSON {
            reply.attr(&TTL, &self.get_projects_file_attributes());
        } else if ino == INODE_USER {
            reply.attr(&TTL, &self.get_user_file_attributes());
        } else {
            let projectid = ino >> 48;
            if projectid > 0 {
                println!("GoodDataFS::getattr() - Project Specific Info {:?}",
                         projectid - 1);
                reply.attr(&TTL, &self.get_project_dir_attributes(ino));
            } else {
                println!("GoodDataFS::getattr() - Not found inode {:?}", ino);
                reply.error(ENOENT);
            }
        }
    }

    fn read(&mut self,
            _req: &Request,
            ino: u64,
            _fh: u64,
            offset: u64,
            _size: u32,
            reply: ReplyData) {
        println!("GoodDataFS::read() - Reading inode {}", ino);
        if ino == INODE_USER {
            let json = format!("{}\n",
                               json::as_pretty_json(&self.client.user()).to_string());
            reply.data(&json.as_bytes()[offset as usize..]);
        } else if ino == INODE_PROJECTS_JSON {
            println!("GoodDataFS::read() - Reading projects.json");
            let json = format!("{}\n",
                               json::as_pretty_json(&self.client.projects()).to_string());
            reply.data(&json.as_bytes()[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir(&mut self,
               _req: &Request,
               ino: u64,
               _fh: u64,
               offset: u64,
               mut reply: ReplyDirectory) {
        println!("GoodDataFS::readdir() - Reading inode {}", ino);
        if ino == INODE_ROOT {
            if offset == 0 {
                // reply.add(INODE_ROOT, 0, FileType::Directory, ".");
                // reply.add(INODE_ROOT, 1, FileType::Directory, "..");

                reply.add(INODE_ROOT, 2, FileType::RegularFile, "user.json");
                reply.add(INODE_ROOT, 3, FileType::Directory, "projects");
            }
            reply.ok();
        } else if ino == INODE_PROJECTS {
            if offset == 0 {
                reply.add(INODE_PROJECTS, 0, FileType::Directory, ".");
                reply.add(INODE_PROJECTS, 1, FileType::Directory, "..");

                self.client.projects_fetch();

                let mut i: u64 = 0;
                let client: &gd::GoodDataClient = self.client();
                let projects = client.projects().as_ref();
                for project in projects.unwrap().into_iter() {
                    let title: &String = project.project()
                        .meta()
                        .title()
                        .as_ref()
                        .unwrap();

                    let inode = (i + 1) << 48;
                    println!("GoodDataFS::readdir() - Adding path {:?}, inode {}",
                             title,
                             inode);
                    // let sanitized = re.replace_all(&title[..], "_");
                    reply.add(INODE_PROJECTS, i + 2, FileType::Directory, title);
                    i += 1;
                }

                reply.add(INODE_PROJECTS_JSON,
                          i + 3,
                          FileType::RegularFile,
                          "projects.json");
            }
            reply.ok();
        } else {
            let projectid = ino >> 48;
            if projectid > 0 {
                if offset == 0 {
                    println!("GoodDataFS::readdir() - Reading project specific inode {}, \
                              projectid {:?}",
                             ino,
                             projectid - 1);

                    reply.add(ino, 0, FileType::Directory, ".");
                    reply.add(ino, 1, FileType::Directory, "..");

                    self.readdir_project(projectid as u16, &mut reply);
                }
                reply.ok();
            } else {
                println!("GoodDataFS::readdir() - Unknown inode {}", ino);
                reply.error(ENOENT);
            }
        }
    }
}

impl GoodDataFS {
    pub fn mount(self, mountpoint: String) {
        println!("Mounting GoodData as Filesystem, mountpoint: {}",
                 mountpoint);

        fuse::mount(self, &mountpoint, &[]);
    }
}
<|endoftext|>


<commit_before>use super::btree::BPlusTree;
use super::sstable::*;
use super::*;
use client::AsyncClient;
use itertools::Itertools;
use parking_lot::RwLock;
use std::sync::Arc;
use std::{mem, ptr};

const LEVEL_PAGES_MULTIPLIER: usize = 1000;
const LEVEL_DIFF_MULTIPLIER: usize = 10;
const LEVEL_M: usize = super::btree::NUM_KEYS;
const LEVEL_1: usize = LEVEL_M * LEVEL_DIFF_MULTIPLIER;
const LEVEL_2: usize = LEVEL_1 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_3: usize = LEVEL_2 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_4: usize = LEVEL_3 * LEVEL_DIFF_MULTIPLIER;
// TODO: debug assert the last one will not overflow MAX_SEGMENT_SIZE

type LevelTrees = Vec<Box<Tree>>;

macro_rules! with_levels {
    ($($sym:ident, $level:ident;)*) => {
        $(
            type $sym = [EntryKey; $level];
            impl_sspage_slice!($sym, EntryKey, $level);
        )*

        fn init_lsm_level_trees(neb_client: &Arc<AsyncClient>) -> (LevelTrees, Vec<usize>) {
            let mut trees = LevelTrees::new();
            let mut sizes = Vec::new();
            $(
                trees.push(box LevelTree::<$sym>::new(neb_client));
                sizes.push($level);
            )*
            return (trees, sizes);
        }
    };
}

with_levels!{
    L1, LEVEL_1;
    L2, LEVEL_2;
    L3, LEVEL_3;
    L4, LEVEL_4;
}

pub struct LSMTree {
    level_m: BPlusTree,
    trees: LevelTrees,
    // use Vec here for convenience
    sizes: Vec<usize>,
}

impl LSMTree {
    pub fn new(neb_client: &Arc<AsyncClient>) -> Self {
        let (trees, sizes) = init_lsm_level_trees(neb_client);
        LSMTree {
            level_m: BPlusTree::new(neb_client),
            trees,
            sizes,
        }
    }

    pub fn insert(&self, mut key: EntryKey, id: &Id) -> Result<(), ()> {
        key_with_id(&mut key, id);
        self.level_m.insert(&key).map_err(|e| ())
    }

    pub fn remove(&self, mut key: EntryKey, id: &Id) -> Result<bool, ()> {
        key_with_id(&mut key, id);
        let m_deleted = self.level_m.remove(&key).map_err(|e| ())?;
        let levels_deleted = self
            .trees
            .iter()
            .map(|tree| tree.mark_deleted(&key))
            .collect_vec() // collect here to prevent short circuit
            .into_iter()
            .any(|d| d);
        Ok(m_deleted || levels_deleted)
    }
}
<commit_msg>lsm tree cursor<commit_after>use super::btree::{BPlusTree, RTCursor as BPlusTreeCursor};
use super::sstable::*;
use super::*;
use client::AsyncClient;
use itertools::Itertools;
use parking_lot::RwLock;
use std::sync::Arc;
use std::{mem, ptr};

const LEVEL_PAGES_MULTIPLIER: usize = 1000;
const LEVEL_DIFF_MULTIPLIER: usize = 10;
const LEVEL_M: usize = super::btree::NUM_KEYS;
const LEVEL_1: usize = LEVEL_M * LEVEL_DIFF_MULTIPLIER;
const LEVEL_2: usize = LEVEL_1 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_3: usize = LEVEL_2 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_4: usize = LEVEL_3 * LEVEL_DIFF_MULTIPLIER;
// TODO: debug assert the last one will not overflow MAX_SEGMENT_SIZE

type LevelTrees = Vec<Box<Tree>>;

macro_rules! with_levels {
    ($($sym:ident, $level:ident;)*) => {
        $(
            type $sym = [EntryKey; $level];
            impl_sspage_slice!($sym, EntryKey, $level);
        )*

        fn init_lsm_level_trees(neb_client: &Arc<AsyncClient>) -> (LevelTrees, Vec<usize>) {
            let mut trees = LevelTrees::new();
            let mut sizes = Vec::new();
            $(
                trees.push(box LevelTree::<$sym>::new(neb_client));
                sizes.push($level);
            )*
            return (trees, sizes);
        }
    };
}

with_levels!{
    L1, LEVEL_1;
    L2, LEVEL_2;
    L3, LEVEL_3;
    L4, LEVEL_4;
}

pub struct LSMTree {
    level_m: BPlusTree,
    trees: LevelTrees,
    // use Vec here for convenience
    sizes: Vec<usize>,
}

impl LSMTree {
    pub fn new(neb_client: &Arc<AsyncClient>) -> Self {
        let (trees, sizes) = init_lsm_level_trees(neb_client);
        LSMTree {
            level_m: BPlusTree::new(neb_client),
            trees,
            sizes,
        }
    }

    pub fn insert(&self, mut key: EntryKey, id: &Id) -> Result<(), ()> {
        key_with_id(&mut key, id);
        self.level_m.insert(&key).map_err(|e| ())
    }

    pub fn remove(&self, mut key: EntryKey, id: &Id) -> Result<bool, ()> {
        key_with_id(&mut key, id);
        let m_deleted = self.level_m.remove(&key).map_err(|e| ())?;
        let levels_deleted = self
            .trees
            .iter()
            .map(|tree| tree.mark_deleted(&key))
            .collect_vec() // collect here to prevent short circuit
            .into_iter()
            .any(|d| d);
        Ok(m_deleted || levels_deleted)
    }
}

pub struct LSMTreeCursor {
    level_cursors: Vec<Box<Cursor>>,
}

impl LSMTreeCursor {
    fn new(cursors: Vec<Box<Cursor>>) -> Self {
        LSMTreeCursor {
            level_cursors: cursors,
        }
    }
}

impl Cursor for LSMTreeCursor {
    fn next(&mut self) -> bool {
        let min_tree = self
            .level_cursors
            .iter()
            .enumerate()
            .map(|(i, cursor)| (i, cursor.current()))
            .filter_map(|(i, current)| current.map(|current_val| (i, current_val)))
            .min_by_key(|(i, val)| *val)
            .map(|(id, _)| id);
        if let Some(id) = min_tree {
            self.level_cursors[id].next();
            return true;
        } else {
            return false;
        }
    }

    fn current(&self) -> Option<&EntryKey> {
        self.level_cursors.iter().filter_map(|c| c.current()).min()
    }
}
<|endoftext|>


<commit_before>use std::io::{self, Read};
use std::mem;

use futures::{Poll, Future};

/// A future which can be used to easily read the entire contents of a stream
/// into a vector.
///
/// Created by the `read_exact` function.
pub struct ReadExact<A, T> {
    state: State<A, T>,
}

enum State<A, T> {
    Reading {
        a: A,
        buf: T,
        pos: usize,
    },
    Empty,
}

/// Creates a future which will read exactly enough bytes to fill `buf`,
/// returning an error if EOF is hit sooner.
///
/// The returned future will resolve to both the I/O stream as well as the
/// buffer once the read operation is completed.
///
/// In the case of an error the buffer and the object will be discarded, with
/// the error yielded. In the case of success the object will be destroyed and
/// the buffer will be returned, with all data read from the stream appended to
/// the buffer.
pub fn read_exact<A, T>(a: A, buf: T) -> ReadExact<A, T>
    where A: Read,
          T: AsMut<[u8]>,
{
    ReadExact {
        state: State::Reading {
            a: a,
            buf: buf,
            pos: 0,
        },
    }
}

fn eof() -> io::Error {
    io::Error::new(io::ErrorKind::UnexpectedEof, "early eof")
}

impl<A, T> Future for ReadExact<A, T>
    where A: Read,
          T: AsMut<[u8]>,
{
    type Item = (A, T);
    type Error = io::Error;

    fn poll(&mut self) -> Poll<(A, T), io::Error> {
        match self.state {
            State::Reading { ref mut a, ref mut buf, ref mut pos } => {
                let buf = buf.as_mut();
                while *pos < buf.len() {
                    let n = try_nb!(a.read(&mut buf[*pos..]));
                    *pos += n;
                    if n == 0 {
                        return Poll::Err(eof())
                    }
                }
            }
            State::Empty => panic!("poll a WriteAll after it's done"),
        }

        match mem::replace(&mut self.state, State::Empty) {
            State::Reading { a, buf, .. } => Poll::Ok((a, buf)),
            State::Empty => panic!(),
        }
    }
}
<commit_msg>fix typo in panic message<commit_after>use std::io::{self, Read};
use std::mem;

use futures::{Poll, Future};

/// A future which can be used to easily read the entire contents of a stream
/// into a vector.
///
/// Created by the `read_exact` function.
pub struct ReadExact<A, T> {
    state: State<A, T>,
}

enum State<A, T> {
    Reading {
        a: A,
        buf: T,
        pos: usize,
    },
    Empty,
}

/// Creates a future which will read exactly enough bytes to fill `buf`,
/// returning an error if EOF is hit sooner.
///
/// The returned future will resolve to both the I/O stream as well as the
/// buffer once the read operation is completed.
///
/// In the case of an error the buffer and the object will be discarded, with
/// the error yielded. In the case of success the object will be destroyed and
/// the buffer will be returned, with all data read from the stream appended to
/// the buffer.
pub fn read_exact<A, T>(a: A, buf: T) -> ReadExact<A, T>
    where A: Read,
          T: AsMut<[u8]>,
{
    ReadExact {
        state: State::Reading {
            a: a,
            buf: buf,
            pos: 0,
        },
    }
}

fn eof() -> io::Error {
    io::Error::new(io::ErrorKind::UnexpectedEof, "early eof")
}

impl<A, T> Future for ReadExact<A, T>
    where A: Read,
          T: AsMut<[u8]>,
{
    type Item = (A, T);
    type Error = io::Error;

    fn poll(&mut self) -> Poll<(A, T), io::Error> {
        match self.state {
            State::Reading { ref mut a, ref mut buf, ref mut pos } => {
                let buf = buf.as_mut();
                while *pos < buf.len() {
                    let n = try_nb!(a.read(&mut buf[*pos..]));
                    *pos += n;
                    if n == 0 {
                        return Poll::Err(eof())
                    }
                }
            }
            State::Empty => panic!("poll a ReadExact after it's done"),
        }

        match mem::replace(&mut self.state, State::Empty) {
            State::Reading { a, buf, .. } => Poll::Ok((a, buf)),
            State::Empty => panic!(),
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::hash_map::HashMap;
use std::string::String;
use std::vec::Vec;
use js_types::js_type::{JsType,JsVar};

#[derive(Clone)]
pub struct JsObjStruct {
    pub proto: JsProto,
    pub name: String,
    pub dict: HashMap<JsVar, JsVar>,
}

impl JsObjStruct {
    pub fn new(proto: JsProto, name: &str, kv_pairs: Vec<(JsVar, JsVar)>) -> JsObjStruct {
        let mut dict = HashMap::new();
        kv_pairs.into_iter().map(|(k,v)| dict.insert(k, v));
        JsObjStruct {
            proto: None,
            name: String::from(name),
            dict: dict,
        }
    }

    pub fn add_key(&mut self, k: JsVar, v: JsVar) {
        self.dict.insert(k, v);
    }
}

pub type JsProto = Option<Box<JsObjStruct>>;

// TODO nice JS object creation macro
//macro_rules! js_obj {
//    ( $kt:ty : $ke:expr => $vt:ty : $ve:expr ),* {
//        {
//
//        }
//    };
//}

#[cfg(test)]
mod tests {
    use super::*;
    use js_types::js_type::{JsType,JsVar};
    use js_types::js_str::{JsStrStruct};

    #[test]
    fn test_js_obj() {
        let mut vec: Vec<(JsVar, JsVar)> = Vec::new();
        for i in 0..10 {
            let k = JsVar::new(JsType::JsNum(i as f64));
            let v = JsVar::new(JsType::JsStr(JsStrStruct::new(
                                            &format!("test{}", i))));
            vec.push((k,v));
        }
        let o = JsObjStruct::new(None, "test", vec);
        for (k, v) in o.dict {
            match k.t {
                JsType::JsNum(ki) => { assert!(ki >= 0.0f64);
                                       assert!(ki < 10.0f64) },
                _ => panic!("Expected a JsNum!"),
            };
            match v.t {
                JsType::JsStr(vs) => assert!(vs.text.starts_with("test")),
                _ => panic!("Expected a JsStr!"),
            };
        }
        assert_eq!(&o.name, "test");
    }

}
<commit_msg>Add method for objects to get their child refs<commit_after>use std::collections::hash_map::HashMap;
use std::collections::hash_set::HashSet;
use std::string::String;
use std::vec::Vec;
use uuid::Uuid;
use js_types::js_type::{JsType,JsVar};

#[derive(Clone)]
pub struct JsObjStruct {
    pub proto: JsProto,
    pub name: String,
    pub dict: HashMap<JsVar, JsVar>,
}

impl JsObjStruct {
    pub fn new(proto: JsProto, name: &str, kv_pairs: Vec<(JsVar, JsVar)>) -> JsObjStruct {
        let mut dict = HashMap::new();
        kv_pairs.into_iter().map(|(k,v)| dict.insert(k, v));
        JsObjStruct {
            proto: None,
            name: String::from(name),
            dict: dict,
        }
    }

    pub fn add_key(&mut self, k: JsVar, v: JsVar) {
        self.dict.insert(k, v);
    }

    pub fn get_children(&self) -> HashSet<Uuid> {
        let mut child_ids = HashSet::new();
        for (k,v) in self.dict.iter() {
            match v.t {
                JsType::JsPtr(ref p) => { child_ids.insert(k.uuid); },
                _ => (),
            }
        }
        child_ids
    }
}

pub type JsProto = Option<Box<JsObjStruct>>;

// TODO nice JS object creation macro
//macro_rules! js_obj {
//    ( $kt:ty : $ke:expr => $vt:ty : $ve:expr ),* {
//        {
//
//        }
//    };
//}

#[cfg(test)]
mod tests {
    use super::*;
    use js_types::js_type::{JsType,JsVar};
    use js_types::js_str::{JsStrStruct};

    #[test]
    fn test_js_obj() {
        let mut vec: Vec<(JsVar, JsVar)> = Vec::new();
        for i in 0..10 {
            let k = JsVar::new(JsType::JsNum(i as f64));
            let v = JsVar::new(JsType::JsStr(JsStrStruct::new(
                                            &format!("test{}", i))));
            vec.push((k,v));
        }
        let o = JsObjStruct::new(None, "test", vec);
        for (k, v) in o.dict {
            match k.t {
                JsType::JsNum(ki) => { assert!(ki >= 0.0f64);
                                       assert!(ki < 10.0f64) },
                _ => panic!("Expected a JsNum!"),
            };
            match v.t {
                JsType::JsStr(vs) => assert!(vs.text.starts_with("test")),
                _ => panic!("Expected a JsStr!"),
            };
        }
        assert_eq!(&o.name, "test");
    }

}
<|endoftext|>


<commit_before>//
//   Copyright 2016 Andrew Hunter
//
//   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.
//

//!
//! # DFA compiler
//!
//! The DFA compiler converts NDFAs into DFAs, using a DFA builder.
//!

use std::marker::PhantomData;
use std::collections::HashSet;
use std::collections::HashMap;
use std::iter::*;
use std::cmp::Ordering;

use super::dfa_builder::*;
use super::state_machine::*;

///
/// Builds a deterministic finite automaton from a NDFA
///
pub struct DfaCompiler<InputSymbol: PartialOrd+Clone, OutputSymbol, DfaType, Ndfa: StateMachine<InputSymbol, OutputSymbol>, Builder: DfaBuilder<InputSymbol, OutputSymbol, DfaType>> {
    /// State machine that is to be compiled
    ndfa: Ndfa,

    /// Builder where the state machine should be generated
    builder: Builder,

    // Phantom data to poke Rust's type system (it's too dumb to see that InputSymbol is used in both Ndfa and Builder there via the type constraint)
    #[allow(dead_code)]
    phantom: (PhantomData<InputSymbol>, PhantomData<OutputSymbol>, PhantomData<DfaType>)
}

/// Represents a state in the DFA (one or more states from the source)
#[derive(Eq, PartialEq, Hash, Clone)]
struct DfaState {
    source_states: Vec<StateId>
}

impl DfaState {
    /// Creates a DFA state from a set of source states
    fn create(source_states: Vec<StateId>) -> DfaState {
        let mut set = HashSet::new();
        for state in source_states {
            set.insert(state);
        }

        DfaState { source_states: Vec::from_iter(set.into_iter()) }
    }

    /// Removes any duplicate states from source_states
    fn dedupe(&mut self) {
        self.source_states.sort();
        self.source_states.dedup();
    }
}

/// Represents the transitions for a source state
struct DfaTransitions<InputSymbol> {
    state_id: StateId,
    transitions: Vec<(InputSymbol, DfaState)>
}

impl<InputSymbol: PartialOrd+Clone> DfaTransitions<InputSymbol> {
    /// Goes through the transitions in the object and merges the states of any with the same symbol
    fn merge_states(&mut self) {
        if self.transitions.len() > 0 {
            // Order the transitions so that if two transitions have the same input symbol, they are neighbours
            self.transitions.sort_by(|a, b| {
                let &(ref symbol_a, _) = a;
                let &(ref symbol_b, _) = b;

                if let Some(result) = symbol_a.partial_cmp(symbol_b) {
                    result
                } else {
                    Ordering::Equal
                }
            });

            // For any transition that has a duplicate state, combine it with the previous state
            let mut new_transitions = vec![];
            new_transitions.push(self.transitions[0].clone());

            for transit_idx in 1..self.transitions.len() {
                let len          = new_transitions.len();
                let this_transit = &mut self.transitions[transit_idx];

                if new_transitions[len-1].0 == this_transit.0 {
                    // Same symbol: expand this state
                    new_transitions[len-1].1.source_states.append(&mut this_transit.1.source_states);
                } else {
                    // Different symbol: add new state
                    new_transitions.push(this_transit.clone());
                }
            }

            // Ensure all of the states are sorted and de-duplicated
            for transit in &mut new_transitions {
                transit.1.dedupe();
            }

            // Update the transitions
            self.transitions = new_transitions;
        }
    }
}

impl<InputSymbol: PartialOrd+Clone, OutputSymbol, DfaType, Ndfa: StateMachine<InputSymbol, OutputSymbol>, Builder: DfaBuilder<InputSymbol, OutputSymbol, DfaType>>
    DfaCompiler<InputSymbol, OutputSymbol, DfaType, Ndfa, Builder> {
    ///
    /// Builds a DFA using an NDFA and a builder
    ///
    pub fn build(ndfa: Ndfa, builder: Builder) -> DfaType {
        let compiler = DfaCompiler::new(ndfa, builder);
        compiler.compile()
    }

    ///
    /// Creates a new DFA compiler using a particular builder and NDFA
    ///
    pub fn new(ndfa: Ndfa, builder: Builder) -> Self {
        DfaCompiler { ndfa: ndfa, builder: builder, phantom: (PhantomData, PhantomData, PhantomData) }
    }

    ///
    /// Compiles the NDFA into a DFA
    ///
    pub fn compile(self) -> DfaType {
        // We assume that input symbols are non-overlapping, which is not automatically the case for symbol ranges

        // Work out the state mapping for each input symbol
        let mut states       = vec![];
        let mut known_states = HashMap::new();
        let mut to_process   = vec![];

        // All state machines have state 0 as their starting state
        let state_zero = DfaState::create(vec![0]);

        states.push(DfaTransitions { state_id: 0, transitions: vec![] });
        known_states.insert(state_zero.clone(), 0);
        to_process.push(state_zero);

        while let Some(state) = to_process.pop() {
            // Create a new transitions object for this state
            let mut transitions = vec![];

            for source_state in &state.source_states {
                let source_transitions = self.ndfa.get_transitions_for_state(*source_state);

                for (symbol, state) in source_transitions {
                    transitions.push((symbol, DfaState::create(vec![state])));
                }
            }

            // Merge it so that we only have one transition per symbol
            let mut dfa_transitions = DfaTransitions { state_id: states.len() as StateId, transitions: transitions};
            dfa_transitions.merge_states();

            // Process any generated states that are not already in the DFA
            for &(_, ref maybe_new_state) in &dfa_transitions.transitions {
                if !known_states.contains_key(maybe_new_state) {
                    to_process.push(maybe_new_state.clone());
                }
            }

            // Store the new state
            known_states.insert(state.clone(), dfa_transitions.state_id);
            states.push(dfa_transitions);
        }

        // Build the DFA
        let mut builder = self.builder;

        for dfa_state in states {
            builder.start_state();

            for (symbol, target_state) in dfa_state.transitions {
                builder.transition(symbol, known_states[&target_state]);
            }
        }

        // TODO: output symbols

        // Generate the final DFA
        builder.build()
    }
}

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

    #[test]
    fn can_create_compiler() {
        let ndfa     = "abc".into_pattern().to_ndfa("success");
        let builder  = SymbolRangeDfaBuilder::new();

        DfaCompiler::new(ndfa, builder);
    }

    #[test]
    fn can_build_dfa() {
        let ndfa     = "abc".into_pattern().to_ndfa("success");
        let builder  = SymbolRangeDfaBuilder::new();

        DfaCompiler::build(ndfa, builder);
    }
}
<commit_msg>Add comment about unordered symbols<commit_after>//
//   Copyright 2016 Andrew Hunter
//
//   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.
//

//!
//! # DFA compiler
//!
//! The DFA compiler converts NDFAs into DFAs, using a DFA builder.
//!

use std::marker::PhantomData;
use std::collections::HashSet;
use std::collections::HashMap;
use std::iter::*;
use std::cmp::Ordering;

use super::dfa_builder::*;
use super::state_machine::*;

///
/// Builds a deterministic finite automaton from a NDFA
///
pub struct DfaCompiler<InputSymbol: PartialOrd+Clone, OutputSymbol, DfaType, Ndfa: StateMachine<InputSymbol, OutputSymbol>, Builder: DfaBuilder<InputSymbol, OutputSymbol, DfaType>> {
    /// State machine that is to be compiled
    ndfa: Ndfa,

    /// Builder where the state machine should be generated
    builder: Builder,

    // Phantom data to poke Rust's type system (it's too dumb to see that InputSymbol is used in both Ndfa and Builder there via the type constraint)
    #[allow(dead_code)]
    phantom: (PhantomData<InputSymbol>, PhantomData<OutputSymbol>, PhantomData<DfaType>)
}

/// Represents a state in the DFA (one or more states from the source)
#[derive(Eq, PartialEq, Hash, Clone)]
struct DfaState {
    source_states: Vec<StateId>
}

impl DfaState {
    /// Creates a DFA state from a set of source states
    fn create(source_states: Vec<StateId>) -> DfaState {
        let mut set = HashSet::new();
        for state in source_states {
            set.insert(state);
        }

        DfaState { source_states: Vec::from_iter(set.into_iter()) }
    }

    /// Removes any duplicate states from source_states
    fn dedupe(&mut self) {
        self.source_states.sort();
        self.source_states.dedup();
    }
}

/// Represents the transitions for a source state
struct DfaTransitions<InputSymbol> {
    state_id: StateId,
    transitions: Vec<(InputSymbol, DfaState)>
}

impl<InputSymbol: PartialOrd+Clone> DfaTransitions<InputSymbol> {
    /// Goes through the transitions in the object and merges the states of any with the same symbol
    fn merge_states(&mut self) {
        if self.transitions.len() > 0 {
            // Order the transitions so that if two transitions have the same input symbol, they are neighbours
            self.transitions.sort_by(|a, b| {
                let &(ref symbol_a, _) = a;
                let &(ref symbol_b, _) = b;

                if let Some(result) = symbol_a.partial_cmp(symbol_b) {
                    result
                } else {
                    // Unordered input symbols won't work well
                    Ordering::Equal
                }
            });

            // For any transition that has a duplicate state, combine it with the previous state
            let mut new_transitions = vec![];
            new_transitions.push(self.transitions[0].clone());

            for transit_idx in 1..self.transitions.len() {
                let len          = new_transitions.len();
                let this_transit = &mut self.transitions[transit_idx];

                if new_transitions[len-1].0 == this_transit.0 {
                    // Same symbol: expand this state
                    new_transitions[len-1].1.source_states.append(&mut this_transit.1.source_states);
                } else {
                    // Different symbol: add new state
                    new_transitions.push(this_transit.clone());
                }
            }

            // Ensure all of the states are sorted and de-duplicated
            for transit in &mut new_transitions {
                transit.1.dedupe();
            }

            // Update the transitions
            self.transitions = new_transitions;
        }
    }
}

impl<InputSymbol: PartialOrd+Clone, OutputSymbol, DfaType, Ndfa: StateMachine<InputSymbol, OutputSymbol>, Builder: DfaBuilder<InputSymbol, OutputSymbol, DfaType>>
    DfaCompiler<InputSymbol, OutputSymbol, DfaType, Ndfa, Builder> {
    ///
    /// Builds a DFA using an NDFA and a builder
    ///
    pub fn build(ndfa: Ndfa, builder: Builder) -> DfaType {
        let compiler = DfaCompiler::new(ndfa, builder);
        compiler.compile()
    }

    ///
    /// Creates a new DFA compiler using a particular builder and NDFA
    ///
    pub fn new(ndfa: Ndfa, builder: Builder) -> Self {
        DfaCompiler { ndfa: ndfa, builder: builder, phantom: (PhantomData, PhantomData, PhantomData) }
    }

    ///
    /// Compiles the NDFA into a DFA
    ///
    pub fn compile(self) -> DfaType {
        // We assume that input symbols are non-overlapping, which is not automatically the case for symbol ranges

        // Work out the state mapping for each input symbol
        let mut states       = vec![];
        let mut known_states = HashMap::new();
        let mut to_process   = vec![];

        // All state machines have state 0 as their starting state
        let state_zero = DfaState::create(vec![0]);

        states.push(DfaTransitions { state_id: 0, transitions: vec![] });
        known_states.insert(state_zero.clone(), 0);
        to_process.push(state_zero);

        while let Some(state) = to_process.pop() {
            // Create a new transitions object for this state
            let mut transitions = vec![];

            for source_state in &state.source_states {
                let source_transitions = self.ndfa.get_transitions_for_state(*source_state);

                for (symbol, state) in source_transitions {
                    transitions.push((symbol, DfaState::create(vec![state])));
                }
            }

            // Merge it so that we only have one transition per symbol
            let mut dfa_transitions = DfaTransitions { state_id: states.len() as StateId, transitions: transitions};
            dfa_transitions.merge_states();

            // Process any generated states that are not already in the DFA
            for &(_, ref maybe_new_state) in &dfa_transitions.transitions {
                if !known_states.contains_key(maybe_new_state) {
                    to_process.push(maybe_new_state.clone());
                }
            }

            // Store the new state
            known_states.insert(state.clone(), dfa_transitions.state_id);
            states.push(dfa_transitions);
        }

        // Build the DFA
        let mut builder = self.builder;

        for dfa_state in states {
            builder.start_state();

            for (symbol, target_state) in dfa_state.transitions {
                builder.transition(symbol, known_states[&target_state]);
            }
        }

        // TODO: output symbols

        // Generate the final DFA
        builder.build()
    }
}

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

    #[test]
    fn can_create_compiler() {
        let ndfa     = "abc".into_pattern().to_ndfa("success");
        let builder  = SymbolRangeDfaBuilder::new();

        DfaCompiler::new(ndfa, builder);
    }

    #[test]
    fn can_build_dfa() {
        let ndfa     = "abc".into_pattern().to_ndfa("success");
        let builder  = SymbolRangeDfaBuilder::new();

        DfaCompiler::build(ndfa, builder);
    }
}
<|endoftext|>