<commit_before>// Copyright 2016 Kyle Mayes
//
// 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.

//! Finds and links to the required `libclang` libraries.

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", warn(clippy))]

extern crate glob;

use std::env;
use std::path::{Path};
use std::process::{Command};

use glob::{MatchOptions};

// Environment variables:
//
// * LLVM_CONFIG_PATH - provides a path to an `llvm-config` executable
// * LIBCLANG_PATH - provides a path to a directory containing a `libclang` shared library
// * LIBCLANG_STATIC_PATH - provides a path to a directory containing LLVM and Clang static libraries

/// Returns whether the supplied directory contains the supplied file.
fn contains<D: AsRef<Path>>(directory: D, file: &str) -> bool {
    directory.as_ref().join(file).exists()
}

/// Panics with a user friendly error message.
fn error(file: &str, env: &str) -> ! {
    panic!("could not find {0}, set the {1} environment variable to a path where {0} can be found", file, env);
}

/// Runs a console command, returning the output if the command was successfully executed.
fn run(command: &str, arguments: &[&str]) -> Option<String> {
    Command::new(command).args(arguments).output().map(|o| {
        String::from_utf8_lossy(&o.stdout).into_owned()
    }).ok()
}

/// Runs `llvm-config`, returning the output if the command was successfully executed.
fn run_llvm_config(arguments: &[&str]) -> Option<String> {
    run(&env::var("LLVM_CONFIG_PATH").unwrap_or("llvm-config".into()), arguments)
}

/// Backup search directory globs for FreeBSD and Linux.
const SEARCH_LINUX: &'static [&'static str] = &[
    "/usr/lib*",
    "/usr/lib*/*",
    "/usr/lib*/*/*",
    "/usr/local/lib*",
    "/usr/local/lib*/*",
    "/usr/local/lib*/*/*",
    "/usr/local/llvm*/lib",
];

/// Backup search directory globs for OS X.
const SEARCH_OSX: &'static [&'static str] = &[
    "/usr/local/opt/llvm*/lib",
    "/Library/Developer/CommandLineTools/usr/lib",
    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
    "/usr/local/opt/llvm*/lib/llvm*/lib",
];

/// Backup search directory globs for Windows.
const SEARCH_WINDOWS: &'static [&'static str] = &[
    "C:\\LLVM\\bin",
    "C:\\LLVM\\lib",
    "C:\\Program Files*\\LLVM\\bin",
    "C:\\Program Files*\\LLVM\\lib",
];

/// Searches for a library, returning the directory it can be found in if the search was successful.
fn find(file: &str, env: &str) -> Option<String> {
    // Search the directory provided by the relevant environment variable, if set.
    if let Some(directory) = env::var(env).ok() {
        if contains(&directory, file) {
            return Some(directory);
        }
    }

    // Search the `bin` and `lib` subdirectories in the directory returned by
    // `llvm-config --prefix`, if `llvm-config` is available.
    if let Some(output) = run_llvm_config(&["--prefix"]) {
        let directory = output.lines().map(|s| s.to_string()).next().unwrap();
        let bin = format!("{}/bin", directory);
        if contains(&bin, file) {
            return Some(bin);
        }
        let lib = format!("{}/lib", directory);
        if contains(&lib, file) {
            return Some(lib);
        }
    }

    // Search the backup directories.
    let search = if cfg!(any(target_os="freebsd", target_os="linux")) {
        SEARCH_LINUX
    } else if cfg!(target_os="macos") {
        SEARCH_OSX
    } else if cfg!(target_os="windows") {
        SEARCH_WINDOWS
    } else {
        return None;
    };
    for pattern in search {
        let mut options = MatchOptions::new();
        options.case_sensitive = false;
        options.require_literal_separator = true;
        if let Ok(paths) = glob::glob_with(pattern, &options) {
            for path in paths.filter_map(Result::ok).filter(|p| p.is_dir()) {
                if contains(&path, file) {
                    return Some(path.to_string_lossy().into_owned());
                }
            }
        }
    }
    None
}

/// Returns the name of an LLVM or Clang library from a path.
fn get_library_name(path: &Path) -> Option<String> {
    path.file_stem().map(|l| l.to_string_lossy()[3..].into())
}

/// Returns the LLVM libraries required to link to `libclang` statically.
fn get_llvm_libraries() -> Vec<String> {
    run_llvm_config(&["--libs"]).expect(
        "could not execute `llvm-config --libs`, set the LLVM_CONFIG_PATH environment variable to \
         a path to an `llvm-config` executable"
    ).split_whitespace().filter_map(|p| {
        // Depending on the version of `llvm-config` in use, listed libraries may be in one of two
        // forms, a full path to the library or simply prefixed with `-l`.
        if p.starts_with("-l") {
            Some(p[2..].into())
        } else {
            get_library_name(Path::new(p))
        }
    }).collect()
}

/// Clang libraries required to link to `libclang` 3.5 and later statically.
const CLANG_LIBRARIES: &'static [&'static str] = &[
    "clang",
    "clangAST",
    "clangAnalysis",
    "clangBasic",
    "clangDriver",
    "clangEdit",
    "clangFrontend",
    "clangIndex",
    "clangLex",
    "clangParse",
    "clangRewrite",
    "clangSema",
    "clangSerialization",
];

/// Returns the Clang libraries required to link to `libclang` statically.
fn get_clang_libraries(directory: &str) -> Vec<String> {
    let pattern = Path::new(directory).join("libclang*.a").to_string_lossy().to_string();
    if let Ok(libraries) = glob::glob(&pattern) {
        libraries.filter_map(|l| l.ok().and_then(|l| get_library_name(&l))).collect()
    } else {
        CLANG_LIBRARIES.iter().map(|l| l.to_string()).collect()
    }
}

fn main() {
    if cfg!(feature="static") {
        // Find LLVM and Clang static libraries.
        let directory = match find("libclang.a", "LIBCLANG_STATIC_PATH") {
            Some(directory) => directory,
            _ => error("libclang.a", "LIBCLANG_STATIC_PATH"),
        };

        print!("cargo:rustc-flags=");

        // Specify required LLVM and Clang static libraries.
        print!("-L {} ", directory);
        for library in get_llvm_libraries() {
            print!("-l static={} ", library);
        }
        for library in get_clang_libraries(&directory) {
            print!("-l static={} ", library);
        }

        // Specify required system libraries.
        if cfg!(any(target_os="freebsd", target_os="linux")) {
            println!("-l ffi -l ncursesw -l stdc++ -l z");
        } else if cfg!(target_os="macos") {
            println!("-l ffi -l ncurses -l stdc++ -l z");
        } else {
            panic!("unsupported operating system for static linking");
        }
    } else {
        let file = if cfg!(target_os="windows") {
            // The filename of the `libclang` shared library on Windows is `libclang.dll` instead of
            // the expected `clang.dll`.
            "libclang.dll".into()
        } else {
            format!("{}clang{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX)
        };

        // Find the `libclang` shared library.
        let directory = match find(&file, "LIBCLANG_PATH") {
            Some(directory) => directory,
            _ => error(&file, "LIBCLANG_PATH")
        };

        println!("cargo:rustc-link-search={}", directory);
        if cfg!(all(target_os="windows", target_env="msvc")) {
            // Find the `libclang` stub static library required for the MSVC toolchain.
            let directory = match find("libclang.lib", "LIBCLANG_PATH") {
                Some(directory) => directory,
                _ => error("libclang.lib", "LIBCLANG_PATH"),
            };

            println!("cargo:rustc-link-search={}", directory);
            println!("cargo:rustc-link-lib=dylib=libclang");
        } else {
            println!("cargo:rustc-link-lib=dylib=clang");
        }
    }
}
<commit_msg>Search additional path on Windows (#34)<commit_after>// Copyright 2016 Kyle Mayes
//
// 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.

//! Finds and links to the required `libclang` libraries.

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", warn(clippy))]

extern crate glob;

use std::env;
use std::path::{Path};
use std::process::{Command};

use glob::{MatchOptions};

// Environment variables:
//
// * LLVM_CONFIG_PATH - provides a path to an `llvm-config` executable
// * LIBCLANG_PATH - provides a path to a directory containing a `libclang` shared library
// * LIBCLANG_STATIC_PATH - provides a path to a directory containing LLVM and Clang static libraries

/// Returns whether the supplied directory contains the supplied file.
fn contains<D: AsRef<Path>>(directory: D, file: &str) -> bool {
    directory.as_ref().join(file).exists()
}

/// Panics with a user friendly error message.
fn error(file: &str, env: &str) -> ! {
    panic!("could not find {0}, set the {1} environment variable to a path where {0} can be found", file, env);
}

/// Runs a console command, returning the output if the command was successfully executed.
fn run(command: &str, arguments: &[&str]) -> Option<String> {
    Command::new(command).args(arguments).output().map(|o| {
        String::from_utf8_lossy(&o.stdout).into_owned()
    }).ok()
}

/// Runs `llvm-config`, returning the output if the command was successfully executed.
fn run_llvm_config(arguments: &[&str]) -> Option<String> {
    run(&env::var("LLVM_CONFIG_PATH").unwrap_or("llvm-config".into()), arguments)
}

/// Backup search directory globs for FreeBSD and Linux.
const SEARCH_LINUX: &'static [&'static str] = &[
    "/usr/lib*",
    "/usr/lib*/*",
    "/usr/lib*/*/*",
    "/usr/local/lib*",
    "/usr/local/lib*/*",
    "/usr/local/lib*/*/*",
    "/usr/local/llvm*/lib",
];

/// Backup search directory globs for OS X.
const SEARCH_OSX: &'static [&'static str] = &[
    "/usr/local/opt/llvm*/lib",
    "/Library/Developer/CommandLineTools/usr/lib",
    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
    "/usr/local/opt/llvm*/lib/llvm*/lib",
];

/// Backup search directory globs for Windows.
const SEARCH_WINDOWS: &'static [&'static str] = &[
    "C:\\LLVM\\bin",
    "C:\\LLVM\\lib",
    "C:\\Program Files*\\LLVM\\bin",
    "C:\\Program Files*\\LLVM\\lib",
];

/// Searches for a library, returning the directory it can be found in if the search was successful.
fn find(file: &str, env: &str) -> Option<String> {
    // Search the directory provided by the relevant environment variable, if set.
    if let Some(directory) = env::var(env).ok() {
        if contains(&directory, file) {
            return Some(directory);
        }
        // On Windows, libclang.dll is usually put in bin directory while
        // libclang.lib is placed under lib directory. So we try to search
        // both directories here.
        if cfg!(target_os="windows") {
            let dir_path = Path::new(&directory);
            let alternative_dir = if dir_path.ends_with("lib") {
                Some("bin")
            } else if dir_path.ends_with("bin") {
                Some("lib")
            } else {
                None
            };
            if let Some(dir) = alternative_dir {
                let alternative_path = dir_path.parent().unwrap().join(dir);
                if contains(&alternative_path, file) {
                    return Some(alternative_path.into_os_string().into_string().unwrap());
                }
            }
        }
    }

    // Search the `bin` and `lib` subdirectories in the directory returned by
    // `llvm-config --prefix`, if `llvm-config` is available.
    if let Some(output) = run_llvm_config(&["--prefix"]) {
        let directory = output.lines().map(|s| s.to_string()).next().unwrap();
        let bin = format!("{}/bin", directory);
        if contains(&bin, file) {
            return Some(bin);
        }
        let lib = format!("{}/lib", directory);
        if contains(&lib, file) {
            return Some(lib);
        }
    }

    // Search the backup directories.
    let search = if cfg!(any(target_os="freebsd", target_os="linux")) {
        SEARCH_LINUX
    } else if cfg!(target_os="macos") {
        SEARCH_OSX
    } else if cfg!(target_os="windows") {
        SEARCH_WINDOWS
    } else {
        return None;
    };
    for pattern in search {
        let mut options = MatchOptions::new();
        options.case_sensitive = false;
        options.require_literal_separator = true;
        if let Ok(paths) = glob::glob_with(pattern, &options) {
            for path in paths.filter_map(Result::ok).filter(|p| p.is_dir()) {
                if contains(&path, file) {
                    return Some(path.to_string_lossy().into_owned());
                }
            }
        }
    }
    None
}

/// Returns the name of an LLVM or Clang library from a path.
fn get_library_name(path: &Path) -> Option<String> {
    path.file_stem().map(|l| l.to_string_lossy()[3..].into())
}

/// Returns the LLVM libraries required to link to `libclang` statically.
fn get_llvm_libraries() -> Vec<String> {
    run_llvm_config(&["--libs"]).expect(
        "could not execute `llvm-config --libs`, set the LLVM_CONFIG_PATH environment variable to \
         a path to an `llvm-config` executable"
    ).split_whitespace().filter_map(|p| {
        // Depending on the version of `llvm-config` in use, listed libraries may be in one of two
        // forms, a full path to the library or simply prefixed with `-l`.
        if p.starts_with("-l") {
            Some(p[2..].into())
        } else {
            get_library_name(Path::new(p))
        }
    }).collect()
}

/// Clang libraries required to link to `libclang` 3.5 and later statically.
const CLANG_LIBRARIES: &'static [&'static str] = &[
    "clang",
    "clangAST",
    "clangAnalysis",
    "clangBasic",
    "clangDriver",
    "clangEdit",
    "clangFrontend",
    "clangIndex",
    "clangLex",
    "clangParse",
    "clangRewrite",
    "clangSema",
    "clangSerialization",
];

/// Returns the Clang libraries required to link to `libclang` statically.
fn get_clang_libraries(directory: &str) -> Vec<String> {
    let pattern = Path::new(directory).join("libclang*.a").to_string_lossy().to_string();
    if let Ok(libraries) = glob::glob(&pattern) {
        libraries.filter_map(|l| l.ok().and_then(|l| get_library_name(&l))).collect()
    } else {
        CLANG_LIBRARIES.iter().map(|l| l.to_string()).collect()
    }
}

fn main() {
    if cfg!(feature="static") {
        // Find LLVM and Clang static libraries.
        let directory = match find("libclang.a", "LIBCLANG_STATIC_PATH") {
            Some(directory) => directory,
            _ => error("libclang.a", "LIBCLANG_STATIC_PATH"),
        };

        print!("cargo:rustc-flags=");

        // Specify required LLVM and Clang static libraries.
        print!("-L {} ", directory);
        for library in get_llvm_libraries() {
            print!("-l static={} ", library);
        }
        for library in get_clang_libraries(&directory) {
            print!("-l static={} ", library);
        }

        // Specify required system libraries.
        if cfg!(any(target_os="freebsd", target_os="linux")) {
            println!("-l ffi -l ncursesw -l stdc++ -l z");
        } else if cfg!(target_os="macos") {
            println!("-l ffi -l ncurses -l stdc++ -l z");
        } else {
            panic!("unsupported operating system for static linking");
        }
    } else {
        let file = if cfg!(target_os="windows") {
            // The filename of the `libclang` shared library on Windows is `libclang.dll` instead of
            // the expected `clang.dll`.
            "libclang.dll".into()
        } else {
            format!("{}clang{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX)
        };

        // Find the `libclang` shared library.
        let directory = match find(&file, "LIBCLANG_PATH") {
            Some(directory) => directory,
            _ => error(&file, "LIBCLANG_PATH")
        };

        println!("cargo:rustc-link-search={}", directory);
        if cfg!(all(target_os="windows", target_env="msvc")) {
            // Find the `libclang` stub static library required for the MSVC toolchain.
            let directory = match find("libclang.lib", "LIBCLANG_PATH") {
                Some(directory) => directory,
                _ => error("libclang.lib", "LIBCLANG_PATH"),
            };

            println!("cargo:rustc-link-search={}", directory);
            println!("cargo:rustc-link-lib=dylib=libclang");
        } else {
            println!("cargo:rustc-link-lib=dylib=clang");
        }
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;

pub fn main() {
    let feature_prefix = "CARGO_FEATURE_";
    let out_dir = env::var("OUT_DIR").unwrap();

    let mut crates = Vec::new();
    for (key, val) in env::vars() {
        if val == "1" && key.starts_with(feature_prefix) {
            let krate = key[feature_prefix.len()..].to_lowercase();
            match krate.as_ref() {
            "default" => continue,
            "all" => continue,
            _ => {},
            }
            crates.push(krate.to_string());
        }
    }
    crates.sort();
    let mut cf = File::create(Path::new(&out_dir).join("uutils_crates.rs")).unwrap();
    let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap();
    mf.write_all("
    type UtilityMap = HashMap<&'static str, fn(Vec<String>) -> i32>;

    fn util_map() -> UtilityMap {
    let mut map: UtilityMap = HashMap::new();\n".as_bytes()).unwrap();
    for krate in crates {
        match krate.as_ref() {
            "false" => continue,
            "true" => continue,
            _ => cf.write_all(format!("extern crate {krate} as uu{krate};\n", krate=krate).as_bytes()).unwrap(),
        }

        match krate.as_ref() {
            "hashsum" => {
                mf.write_all("map.insert(\"hashsum\", uuhashsum::uumain);
                              map.insert(\"md5sum\", uuhashsum::uumain);
                              map.insert(\"sha1sum\", uuhashsum::uumain);
                              map.insert(\"sha224sum\", uuhashsum::uumain);
                              map.insert(\"sha256sum\", uuhashsum::uumain);
                              map.insert(\"sha384sum\", uuhashsum::uumain);
                              map.insert(\"sha512sum\", uuhashsum::uumain);\n".as_bytes()).unwrap();
            },
            "false" =>
                mf.write_all("fn uufalse(_: Vec<String>) -> i32 { 1 }
                             map.insert(\"false\", uufalse as fn(Vec<String>) -> i32);\n".as_bytes()).unwrap(),
            "true" =>
                mf.write_all("fn uutrue(_: Vec<String>) -> i32 { 0 }
                             map.insert(\"true\", uutrue as fn(Vec<String>) -> i32);\n".as_bytes()).unwrap(),
            _ =>
                mf.write_all(format!("map.insert(\"{krate}\", uu{krate}::uumain as fn(Vec<String>) -> i32);\n", krate= krate).as_bytes()).unwrap(),
        }
    }
    mf.write_all("map
    }\n".as_bytes()).unwrap();
    cf.flush().unwrap();
    mf.flush().unwrap();
}
<commit_msg>Add true/false back to multicall binary<commit_after>use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;

pub fn main() {
    let feature_prefix = "CARGO_FEATURE_";
    let out_dir = env::var("OUT_DIR").unwrap();

    let mut crates = Vec::new();
    for (key, val) in env::vars() {
        if val == "1" && key.starts_with(feature_prefix) {
            let krate = key[feature_prefix.len()..].to_lowercase();
            match krate.as_ref() {
            "default" => continue,
            "all" => continue,
            _ => {},
            }
            crates.push(krate.to_string());
        }
    }
    crates.sort();
    let mut cf = File::create(Path::new(&out_dir).join("uutils_crates.rs")).unwrap();
    let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap();
    mf.write_all("
    type UtilityMap = HashMap<&'static str, fn(Vec<String>) -> i32>;

    fn util_map() -> UtilityMap {
    let mut map: UtilityMap = HashMap::new();\n".as_bytes()).unwrap();
    for krate in crates {
        match krate.as_ref() {
            "false" => {},
            "true" => {},
            _ => cf.write_all(format!("extern crate {krate} as uu{krate};\n", krate=krate).as_bytes()).unwrap(),
        }

        match krate.as_ref() {
            "hashsum" => {
                mf.write_all("map.insert(\"hashsum\", uuhashsum::uumain);
                              map.insert(\"md5sum\", uuhashsum::uumain);
                              map.insert(\"sha1sum\", uuhashsum::uumain);
                              map.insert(\"sha224sum\", uuhashsum::uumain);
                              map.insert(\"sha256sum\", uuhashsum::uumain);
                              map.insert(\"sha384sum\", uuhashsum::uumain);
                              map.insert(\"sha512sum\", uuhashsum::uumain);\n".as_bytes()).unwrap();
            },
            "false" =>
                mf.write_all("fn uufalse(_: Vec<String>) -> i32 { 1 }
                             map.insert(\"false\", uufalse as fn(Vec<String>) -> i32);\n".as_bytes()).unwrap(),
            "true" =>
                mf.write_all("fn uutrue(_: Vec<String>) -> i32 { 0 }
                             map.insert(\"true\", uutrue as fn(Vec<String>) -> i32);\n".as_bytes()).unwrap(),
            _ =>
                mf.write_all(format!("map.insert(\"{krate}\", uu{krate}::uumain as fn(Vec<String>) -> i32);\n", krate= krate).as_bytes()).unwrap(),
        }
    }
    mf.write_all("map
    }\n".as_bytes()).unwrap();
    cf.flush().unwrap();
    mf.flush().unwrap();
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// 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.

//! Providers
//!
//! These are the providers which coreos-metadata knows how to retrieve metadata
//! from. Internally, they handle the ins and outs of each providers metadata
//! services, and externally, they provide a function to fetch that metadata in
//! a regular format.
//!
//! To add a provider, put a `pub mod provider;` line in this file, export a
//! function to fetch the metadata, and then add a match line in the top-level
//! `fetch_metadata()` function in metadata.rs.

pub mod azure;
pub mod digitalocean;
pub mod cloudstack;
pub mod ec2;
pub mod gce;
pub mod openstack;
pub mod packet;
pub mod vagrant_virtualbox;
<commit_msg>providers: add MetadataProvider trait<commit_after>// Copyright 2017 CoreOS, Inc.
//
// 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.

//! Providers
//!
//! These are the providers which coreos-metadata knows how to retrieve metadata
//! from. Internally, they handle the ins and outs of each providers metadata
//! services, and externally, they provide a function to fetch that metadata in
//! a regular format.
//!
//! To add a provider, put a `pub mod provider;` line in this file, export a
//! function to fetch the metadata, and then add a match line in the top-level
//! `fetch_metadata()` function in metadata.rs.

pub mod azure;
pub mod digitalocean;
pub mod cloudstack;
pub mod ec2;
pub mod gce;
pub mod openstack;
pub mod packet;
pub mod vagrant_virtualbox;

use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;

use update_ssh_keys::{AuthorizedKeys, AuthorizedKeyEntry};
use users;

use errors::*;
use network;

fn create_file(filename: &str) -> Result<File> {
    let file_path = Path::new(&filename);
    // create the directories if they don't exist
    let folder = file_path.parent()
        .ok_or_else(|| format!("could not get parent directory of {:?}", file_path))?;
    fs::create_dir_all(&folder)
        .chain_err(|| format!("failed to create directory {:?}", folder))?;
    // create (or truncate) the file we want to write to
    File::create(file_path)
        .chain_err(|| format!("failed to create file {:?}", file_path))
}

pub trait MetadataProvider {
    fn attributes(&self) -> Result<HashMap<String, String>>;
    fn hostname(&self) -> Result<Option<String>>;
    fn ssh_keys(&self) -> Result<Vec<AuthorizedKeyEntry>>;
    fn networks(&self) -> Result<Vec<network::Interface>>;
    fn network_devices(&self) -> Result<Vec<network::Device>>;

    fn write_attributes(&self, attributes_file_path: String) -> Result<()> {
        let mut attributes_file = create_file(&attributes_file_path)?;
        for (k,v) in self.attributes()? {
            write!(&mut attributes_file, "COREOS_{}={}\n", k, v)
                .chain_err(|| format!("failed to write attributes to file {:?}", attributes_file))?;
        }
        Ok(())
    }

    fn write_ssh_keys(&self, ssh_keys_user: String) -> Result<()> {
        // find the ssh keys user and open their ssh authorized keys directory
        let user = users::get_user_by_name(&ssh_keys_user)
            .ok_or_else(|| format!("could not find user with username {:?}", ssh_keys_user))?;
        let mut authorized_keys_dir = AuthorizedKeys::open(user, true, None)
            .chain_err(|| format!("failed to open authorzied keys directory for user '{}'", ssh_keys_user))?;

        // add the ssh keys to the directory
        authorized_keys_dir.add_keys("coreos-metadata", self.ssh_keys()?, true, true)?;

        // write the changes and sync the directory
        authorized_keys_dir.write()
            .chain_err(|| "failed to update authorized keys directory")?;
        authorized_keys_dir.sync()
            .chain_err(|| "failed to update authorized keys")
    }

    fn write_hostname(&self, hostname_file_path: String) -> Result<()> {
        match self.hostname()? {
            Some(ref hostname) => {
                let mut hostname_file = create_file(&hostname_file_path)?;
                write!(&mut hostname_file, "{}\n", hostname)
                    .chain_err(|| format!("failed to write hostname {:?} to file {:?}", hostname, hostname_file))
            }
            None => Ok(())
        }
    }

    fn write_network_units(&self, network_units_dir: String) -> Result<()> {
        let dir_path = Path::new(&network_units_dir);
        fs::create_dir_all(&dir_path)
            .chain_err(|| format!("failed to create directory {:?}", dir_path))?;
        for interface in &self.networks()? {
            let file_path = dir_path.join(interface.unit_name());
            let mut unit_file = File::create(&file_path)
                .chain_err(|| format!("failed to create file {:?}", file_path))?;
            write!(&mut unit_file, "{}", interface.config())
                .chain_err(|| format!("failed to write network interface unit file {:?}", unit_file))?;
        }
        for device in &self.network_devices()? {
            let file_path = dir_path.join(device.unit_name());
            let mut unit_file = File::create(&file_path)
                .chain_err(|| format!("failed to create file {:?}", file_path))?;
            write!(&mut unit_file, "{}", device.config())
                .chain_err(|| format!("failed to write network device unit file {:?}", unit_file))?;
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// 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.

//! Providers
//!
//! These are the providers which Afterburn knows how to retrieve metadata
//! from. Internally, they handle the ins and outs of each providers metadata
//! services, and externally, they provide a function to fetch that metadata in
//! a regular format.
//!
//! To add a provider, put a `pub mod provider;` line in this file, export a
//! function to fetch the metadata, and then add a match line in the top-level
//! `fetch_metadata()` function in metadata.rs.

pub mod aliyun;
pub mod aws;
pub mod azure;
pub mod cloudstack;
pub mod digitalocean;
pub mod exoscale;
pub mod gcp;
pub mod ibmcloud;
pub mod ibmcloud_classic;
pub mod openstack;
pub mod packet;
pub mod vagrant_virtualbox;
pub mod vmware;

use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;

use openssh_keys::PublicKey;
use users::{self, User};

use crate::errors::*;
use crate::network;

#[cfg(not(feature = "cl-legacy"))]
const ENV_PREFIX: &str = "AFTERBURN_";
#[cfg(feature = "cl-legacy")]
const ENV_PREFIX: &str = "COREOS_";

fn create_file(filename: &str) -> Result<File> {
    let file_path = Path::new(&filename);
    // create the directories if they don't exist
    let folder = file_path
        .parent()
        .ok_or_else(|| format!("could not get parent directory of {:?}", file_path))?;
    fs::create_dir_all(&folder).chain_err(|| format!("failed to create directory {:?}", folder))?;
    // create (or truncate) the file we want to write to
    File::create(file_path).chain_err(|| format!("failed to create file {:?}", file_path))
}

#[cfg(feature = "cl-legacy")]
fn write_ssh_keys(user: User, ssh_keys: Vec<PublicKey>) -> Result<()> {
    use update_ssh_keys::{AuthorizedKeyEntry, AuthorizedKeys};

    // If we don't have any SSH keys, don't bother trying to write them as
    // update-ssh-keys will yell at us.
    if !ssh_keys.is_empty() {
        // open the user's authorized keys directory
        let user_name = user.name().to_string_lossy().into_owned();
        let mut authorized_keys_dir = AuthorizedKeys::open(user, true, None).chain_err(|| {
            format!(
                "failed to open authorized keys directory for user '{}'",
                user_name
            )
        })?;

        // add the ssh keys to the directory
        let entries = ssh_keys
            .into_iter()
            .map(|key| AuthorizedKeyEntry::Valid { key })
            .collect::<Vec<_>>();
        // legacy name for legacy mode
        authorized_keys_dir.add_keys("coreos-metadata", entries, true, true)?;

        // write the changes and sync the directory
        authorized_keys_dir
            .write()
            .chain_err(|| "failed to update authorized keys directory")?;
        authorized_keys_dir
            .sync()
            .chain_err(|| "failed to update authorized keys")?;
    }

    Ok(())
}

#[cfg(not(feature = "cl-legacy"))]
fn write_ssh_keys(user: User, ssh_keys: Vec<PublicKey>) -> Result<()> {
    use std::io::ErrorKind::NotFound;
    use users::os::unix::UserExt;

    // switch users
    let _guard = users::switch::switch_user_group(user.uid(), user.primary_group_id())
        .chain_err(|| "failed to switch user/group")?;

    // get paths
    let dir_path = user.home_dir().join(".ssh").join("authorized_keys.d");
    let file_name = "afterburn";
    let file_path = &dir_path.join(file_name);

    if !ssh_keys.is_empty() {
        // ensure directory exists
        fs::create_dir_all(&dir_path)
            .chain_err(|| format!("failed to create directory {:?}", &dir_path))?;

        // create temporary file
        let mut temp_file = tempfile::Builder::new()
            .prefix(&format!(".{}-", file_name))
            .tempfile_in(&dir_path)
            .chain_err(|| "failed to create temporary file")?;

        // write out keys
        for key in ssh_keys {
            writeln!(temp_file, "{}", key).chain_err(|| {
                format!("failed to write to file {:?}", temp_file.path().display())
            })?;
        }

        // sync to disk
        temp_file
            .as_file()
            .sync_all()
            .chain_err(|| format!("failed to sync file {:?}", temp_file.path().display()))?;

        // atomically rename to destination
        // don't leak temporary file on error
        temp_file
            .persist(&file_path)
            .map_err(|e| {
                e.file.close().ok();
                e.error
            })
            .chain_err(|| format!("failed to persist file {:?}", file_path.display()))?;
    } else {
        // delete the file
        match fs::remove_file(&file_path) {
            Err(ref e) if e.kind() == NotFound => Ok(()),
            other => other,
        }
        .chain_err(|| format!("failed to remove file {:?}", file_path.display()))?;
    }

    // sync parent dir to persist updates
    match File::open(&dir_path) {
        Ok(dir_file) => dir_file.sync_all(),
        Err(ref e) if e.kind() == NotFound => Ok(()),
        Err(e) => Err(e),
    }
    .chain_err(|| format!("failed to sync '{}'", dir_path.display()))?;

    // make clippy happy while fulfilling our interface
    drop(user);

    Ok(())
}

pub trait MetadataProvider {
    fn attributes(&self) -> Result<HashMap<String, String>>;
    fn hostname(&self) -> Result<Option<String>>;
    fn ssh_keys(&self) -> Result<Vec<PublicKey>>;
    fn networks(&self) -> Result<Vec<network::Interface>>;
    fn boot_checkin(&self) -> Result<()>;

    /// Return a list of virtual network devices for this machine.
    ///
    /// This is used to setup virtual interfaces, e.g. via [systemd.netdev][netdev]
    /// configuration fragments.
    ///
    /// netdev: https://www.freedesktop.org/software/systemd/man/systemd.netdev.html
    fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>>;

    fn write_attributes(&self, attributes_file_path: String) -> Result<()> {
        let mut attributes_file = create_file(&attributes_file_path)?;
        for (k, v) in self.attributes()? {
            writeln!(&mut attributes_file, "{}{}={}", ENV_PREFIX, k, v).chain_err(|| {
                format!("failed to write attributes to file {:?}", attributes_file)
            })?;
        }
        Ok(())
    }

    fn write_ssh_keys(&self, ssh_keys_user: String) -> Result<()> {
        let ssh_keys = self.ssh_keys()?;
        let user = users::get_user_by_name(&ssh_keys_user)
            .ok_or_else(|| format!("could not find user with username {:?}", ssh_keys_user))?;

        write_ssh_keys(user, ssh_keys)?;

        Ok(())
    }

    fn write_hostname(&self, hostname_file_path: String) -> Result<()> {
        match self.hostname()? {
            Some(ref hostname) => {
                let mut hostname_file = create_file(&hostname_file_path)?;
                writeln!(&mut hostname_file, "{}", hostname).chain_err(|| {
                    format!(
                        "failed to write hostname {:?} to file {:?}",
                        hostname, hostname_file
                    )
                })
            }
            None => Ok(()),
        }
    }

    fn write_network_units(&self, network_units_dir: String) -> Result<()> {
        let dir_path = Path::new(&network_units_dir);
        fs::create_dir_all(&dir_path)
            .chain_err(|| format!("failed to create directory {:?}", dir_path))?;

        // Write `.network` fragments for network interfaces/links.
        for interface in &self.networks()? {
            let unit_name = interface.sd_network_unit_name()?;
            let file_path = dir_path.join(unit_name);
            let mut unit_file = File::create(&file_path)
                .chain_err(|| format!("failed to create file {:?}", file_path))?;
            write!(&mut unit_file, "{}", interface.config()).chain_err(|| {
                format!(
                    "failed to write network interface unit file {:?}",
                    unit_file
                )
            })?;
        }

        // Write `.netdev` fragments for virtual network devices.
        for device in &self.virtual_network_devices()? {
            let file_path = dir_path.join(device.netdev_unit_name());
            let mut unit_file = File::create(&file_path)
                .chain_err(|| format!("failed to create netdev unit file {:?}", file_path))?;
            write!(&mut unit_file, "{}", device.sd_netdev_config())
                .chain_err(|| format!("failed to write netdev unit file {:?}", unit_file))?;
        }
        Ok(())
    }
}
<commit_msg>Add logic for sending structured ssh_keys_info into the journald<commit_after>// Copyright 2017 CoreOS, Inc.
//
// 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.

//! Providers
//!
//! These are the providers which Afterburn knows how to retrieve metadata
//! from. Internally, they handle the ins and outs of each providers metadata
//! services, and externally, they provide a function to fetch that metadata in
//! a regular format.
//!
//! To add a provider, put a `pub mod provider;` line in this file, export a
//! function to fetch the metadata, and then add a match line in the top-level
//! `fetch_metadata()` function in metadata.rs.

pub mod aliyun;
pub mod aws;
pub mod azure;
pub mod cloudstack;
pub mod digitalocean;
pub mod exoscale;
pub mod gcp;
pub mod ibmcloud;
pub mod ibmcloud_classic;
pub mod openstack;
pub mod packet;
pub mod vagrant_virtualbox;
pub mod vmware;

use libsystemd::logging;
use openssh_keys::PublicKey;
use slog_scope::warn;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;
use users::{self, User};

use crate::errors::*;
use crate::network;

#[cfg(not(feature = "cl-legacy"))]
const ENV_PREFIX: &str = "AFTERBURN_";
#[cfg(feature = "cl-legacy")]
const ENV_PREFIX: &str = "COREOS_";
/// Message ID marker for authorized-keys entries in journal.
const AFTERBURN_SSH_AUTHORIZED_KEYS_MESSAGEID: &str = "0f7d7a502f2d433caa1323440a6b4190";

fn create_file(filename: &str) -> Result<File> {
    let file_path = Path::new(&filename);
    // create the directories if they don't exist
    let folder = file_path
        .parent()
        .ok_or_else(|| format!("could not get parent directory of {:?}", file_path))?;
    fs::create_dir_all(&folder).chain_err(|| format!("failed to create directory {:?}", folder))?;
    // create (or truncate) the file we want to write to
    File::create(file_path).chain_err(|| format!("failed to create file {:?}", file_path))
}

/// Add a message to the journal logging SSH key additions; this
/// will be used by at least Fedora CoreOS to display in the console
/// if no ssh keys are present.
fn write_ssh_key_journal_entry(log: logging::Priority, name: &str, path: &str) {
    let message = format!("wrote ssh authorized keys file for user: {}", name);
    let map = maplit::hashmap! {
        "AFTERBURN_USER_NAME" => name.as_ref(),
        "AFTERBURN_PATH" => path.as_ref(),
        "MESSAGE_ID" => AFTERBURN_SSH_AUTHORIZED_KEYS_MESSAGEID,
    };
    if let Err(e) = logging::journal_send(log, &message, map.iter()) {
        warn!("failed to send information to journald: {}", e);
    }
}
#[cfg(feature = "cl-legacy")]
fn write_ssh_keys(user: User, ssh_keys: Vec<PublicKey>) -> Result<()> {
    use update_ssh_keys::{AuthorizedKeyEntry, AuthorizedKeys};

    // If we don't have any SSH keys, don't bother trying to write them as
    // update-ssh-keys will yell at us.
    if !ssh_keys.is_empty() {
        // open the user's authorized keys directory
        let user_name = user.name().to_string_lossy().into_owned();
        let mut authorized_keys_dir = AuthorizedKeys::open(user, true, None).chain_err(|| {
            format!(
                "failed to open authorized keys directory for user '{}'",
                user_name
            )
        })?;

        // add the ssh keys to the directory
        let entries = ssh_keys
            .into_iter()
            .map(|key| AuthorizedKeyEntry::Valid { key })
            .collect::<Vec<_>>();
        // legacy name for legacy mode
        authorized_keys_dir.add_keys("coreos-metadata", entries, true, true)?;

        // write the changes and sync the directory
        authorized_keys_dir
            .write()
            .chain_err(|| "failed to update authorized keys directory")?;
        authorized_keys_dir
            .sync()
            .chain_err(|| "failed to update authorized keys")?;
        let path = authorized_keys_dir.ssh_dir.display().to_string();
        write_ssh_key_journal_entry(logging::Priority::Info, &user_name, &path);
    }

    Ok(())
}

#[cfg(not(feature = "cl-legacy"))]
fn write_ssh_keys(user: User, ssh_keys: Vec<PublicKey>) -> Result<()> {
    use std::io::ErrorKind::NotFound;
    use users::os::unix::UserExt;

    // switch users
    let _guard = users::switch::switch_user_group(user.uid(), user.primary_group_id())
        .chain_err(|| "failed to switch user/group")?;

    // get paths
    let dir_path = user.home_dir().join(".ssh").join("authorized_keys.d");
    let file_name = "afterburn";
    let file_path = &dir_path.join(file_name);

    if !ssh_keys.is_empty() {
        // ensure directory exists
        fs::create_dir_all(&dir_path)
            .chain_err(|| format!("failed to create directory {:?}", &dir_path))?;

        // create temporary file
        let mut temp_file = tempfile::Builder::new()
            .prefix(&format!(".{}-", file_name))
            .tempfile_in(&dir_path)
            .chain_err(|| "failed to create temporary file")?;

        // write out keys
        for key in ssh_keys {
            writeln!(temp_file, "{}", key).chain_err(|| {
                format!("failed to write to file {:?}", temp_file.path().display())
            })?;
        }

        // sync to disk
        temp_file
            .as_file()
            .sync_all()
            .chain_err(|| format!("failed to sync file {:?}", temp_file.path().display()))?;

        // atomically rename to destination
        // don't leak temporary file on error
        temp_file
            .persist(&file_path)
            .map_err(|e| {
                e.file.close().ok();
                e.error
            })
            .chain_err(|| format!("failed to persist file {:?}", file_path.display()))?;
    } else {
        // delete the file
        match fs::remove_file(&file_path) {
            Err(ref e) if e.kind() == NotFound => Ok(()),
            other => other,
        }
        .chain_err(|| format!("failed to remove file {:?}", file_path.display()))?;
    }

    let username = user.name().to_string_lossy();
    let path = file_path.to_string_lossy();
    write_ssh_key_journal_entry(logging::Priority::Info, &username, &path);

    // sync parent dir to persist updates
    match File::open(&dir_path) {
        Ok(dir_file) => dir_file.sync_all(),
        Err(ref e) if e.kind() == NotFound => Ok(()),
        Err(e) => Err(e),
    }
    .chain_err(|| format!("failed to sync '{}'", dir_path.display()))?;

    // make clippy happy while fulfilling our interface
    drop(user);

    Ok(())
}

pub trait MetadataProvider {
    fn attributes(&self) -> Result<HashMap<String, String>>;
    fn hostname(&self) -> Result<Option<String>>;
    fn ssh_keys(&self) -> Result<Vec<PublicKey>>;
    fn networks(&self) -> Result<Vec<network::Interface>>;
    fn boot_checkin(&self) -> Result<()>;

    /// Return a list of virtual network devices for this machine.
    ///
    /// This is used to setup virtual interfaces, e.g. via [systemd.netdev][netdev]
    /// configuration fragments.
    ///
    /// netdev: https://www.freedesktop.org/software/systemd/man/systemd.netdev.html
    fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>>;

    fn write_attributes(&self, attributes_file_path: String) -> Result<()> {
        let mut attributes_file = create_file(&attributes_file_path)?;
        for (k, v) in self.attributes()? {
            writeln!(&mut attributes_file, "{}{}={}", ENV_PREFIX, k, v).chain_err(|| {
                format!("failed to write attributes to file {:?}", attributes_file)
            })?;
        }
        Ok(())
    }

    fn write_ssh_keys(&self, ssh_keys_user: String) -> Result<()> {
        let ssh_keys = self.ssh_keys()?;
        let user = users::get_user_by_name(&ssh_keys_user)
            .ok_or_else(|| format!("could not find user with username {:?}", ssh_keys_user))?;

        write_ssh_keys(user, ssh_keys)?;

        Ok(())
    }

    fn write_hostname(&self, hostname_file_path: String) -> Result<()> {
        match self.hostname()? {
            Some(ref hostname) => {
                let mut hostname_file = create_file(&hostname_file_path)?;
                writeln!(&mut hostname_file, "{}", hostname).chain_err(|| {
                    format!(
                        "failed to write hostname {:?} to file {:?}",
                        hostname, hostname_file
                    )
                })
            }
            None => Ok(()),
        }
    }

    fn write_network_units(&self, network_units_dir: String) -> Result<()> {
        let dir_path = Path::new(&network_units_dir);
        fs::create_dir_all(&dir_path)
            .chain_err(|| format!("failed to create directory {:?}", dir_path))?;

        // Write `.network` fragments for network interfaces/links.
        for interface in &self.networks()? {
            let unit_name = interface.sd_network_unit_name()?;
            let file_path = dir_path.join(unit_name);
            let mut unit_file = File::create(&file_path)
                .chain_err(|| format!("failed to create file {:?}", file_path))?;
            write!(&mut unit_file, "{}", interface.config()).chain_err(|| {
                format!(
                    "failed to write network interface unit file {:?}",
                    unit_file
                )
            })?;
        }

        // Write `.netdev` fragments for virtual network devices.
        for device in &self.virtual_network_devices()? {
            let file_path = dir_path.join(device.netdev_unit_name());
            let mut unit_file = File::create(&file_path)
                .chain_err(|| format!("failed to create netdev unit file {:?}", file_path))?;
            write!(&mut unit_file, "{}", device.sd_netdev_config())
                .chain_err(|| format!("failed to write netdev unit file {:?}", unit_file))?;
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>extern crate libanalysis;
extern crate relative_path;
extern crate test_utils;

use std::path::{Path};

use relative_path::RelativePath;
use libanalysis::{AnalysisHost, FileId, FileResolver};
use test_utils::assert_eq_dbg;

struct FileMap(&'static [(u32, &'static str)]);

impl FileMap {
    fn path(&self, id: FileId) -> &'static Path {
        let s = self.0.iter()
            .find(|it| it.0 == id.0)
            .unwrap()
            .1;
        Path::new(s)
    }
}

impl FileResolver for FileMap {
    fn file_stem(&self, id: FileId) -> String {
        self.path(id).file_stem().unwrap().to_str().unwrap().to_string()
    }
    fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> {
        let path = rel.to_path(self.path(id));
        let path = path.to_str().unwrap();
        let path = RelativePath::new(&path[1..]).normalize();
        let &(id, _) = self.0.iter()
            .find(|it| path == RelativePath::new(&it.1[1..]).normalize())?;
        Some(FileId(id))
    }
}

#[test]
fn test_resolve_module() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo;".to_string()));
    world.change_file(FileId(2), Some("".to_string()));

    let snap = world.analysis(FileMap(&[
        (1, "/lib.rs"),
        (2, "/foo.rs"),
    ]));
    let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into());
    assert_eq_dbg(
        r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#,
        &symbols,
    );

    let snap = world.analysis(FileMap(&[
        (1, "/lib.rs"),
        (2, "/foo/mod.rs")
    ]));
    let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into());
    assert_eq_dbg(
        r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#,
        &symbols,
    );
}

#[test]
fn test_unresolved_module_diagnostic() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo;".to_string()));

    let snap = world.analysis(FileMap(&[(1, "/lib.rs")]));
    let diagnostics = snap.diagnostics(FileId(1));
    assert_eq_dbg(
        r#"[Diagnostic {
            message: "unresolved module",
            range: [4; 7),
            fix: Some(SourceChange {
                label: "create module",
                source_file_edits: [],
                file_system_edits: [CreateFile { anchor: FileId(1), path: "../foo.rs" }],
                cursor_position: None }) }]"#,
        &diagnostics,
    );
}

#[test]
fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo {}".to_string()));

    let snap = world.analysis(FileMap(&[(1, "/lib.rs")]));
    let diagnostics = snap.diagnostics(FileId(1));
    assert_eq_dbg(
        r#"[]"#,
        &diagnostics,
    );
}

#[test]
fn test_resolve_parent_module() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo;".to_string()));
    world.change_file(FileId(2), Some("".to_string()));

    let snap = world.analysis(FileMap(&[
        (1, "/lib.rs"),
        (2, "/foo.rs"),
    ]));
    let symbols = snap.parent_module(FileId(2));
    assert_eq_dbg(
        r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
        &symbols,
    );
}
<commit_msg>fix tests<commit_after>extern crate libanalysis;
extern crate relative_path;
extern crate test_utils;

use std::path::{Path};

use relative_path::RelativePath;
use libanalysis::{AnalysisHost, FileId, FileResolver, JobHandle};
use test_utils::assert_eq_dbg;

struct FileMap(&'static [(u32, &'static str)]);

impl FileMap {
    fn path(&self, id: FileId) -> &'static Path {
        let s = self.0.iter()
            .find(|it| it.0 == id.0)
            .unwrap()
            .1;
        Path::new(s)
    }
}

impl FileResolver for FileMap {
    fn file_stem(&self, id: FileId) -> String {
        self.path(id).file_stem().unwrap().to_str().unwrap().to_string()
    }
    fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> {
        let path = rel.to_path(self.path(id));
        let path = path.to_str().unwrap();
        let path = RelativePath::new(&path[1..]).normalize();
        let &(id, _) = self.0.iter()
            .find(|it| path == RelativePath::new(&it.1[1..]).normalize())?;
        Some(FileId(id))
    }
}

#[test]
fn test_resolve_module() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo;".to_string()));
    world.change_file(FileId(2), Some("".to_string()));

    let snap = world.analysis(FileMap(&[
        (1, "/lib.rs"),
        (2, "/foo.rs"),
    ]));
    let (_handle, token) = JobHandle::new();
    let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into(), &token);
    assert_eq_dbg(
        r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#,
        &symbols,
    );

    let snap = world.analysis(FileMap(&[
        (1, "/lib.rs"),
        (2, "/foo/mod.rs")
    ]));
    let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into(), &token);
    assert_eq_dbg(
        r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#,
        &symbols,
    );
}

#[test]
fn test_unresolved_module_diagnostic() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo;".to_string()));

    let snap = world.analysis(FileMap(&[(1, "/lib.rs")]));
    let diagnostics = snap.diagnostics(FileId(1));
    assert_eq_dbg(
        r#"[Diagnostic {
            message: "unresolved module",
            range: [4; 7),
            fix: Some(SourceChange {
                label: "create module",
                source_file_edits: [],
                file_system_edits: [CreateFile { anchor: FileId(1), path: "../foo.rs" }],
                cursor_position: None }) }]"#,
        &diagnostics,
    );
}

#[test]
fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo {}".to_string()));

    let snap = world.analysis(FileMap(&[(1, "/lib.rs")]));
    let diagnostics = snap.diagnostics(FileId(1));
    assert_eq_dbg(
        r#"[]"#,
        &diagnostics,
    );
}

#[test]
fn test_resolve_parent_module() {
    let mut world = AnalysisHost::new();
    world.change_file(FileId(1), Some("mod foo;".to_string()));
    world.change_file(FileId(2), Some("".to_string()));

    let snap = world.analysis(FileMap(&[
        (1, "/lib.rs"),
        (2, "/foo.rs"),
    ]));
    let symbols = snap.parent_module(FileId(2));
    assert_eq_dbg(
        r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
        &symbols,
    );
}
<|endoftext|>


<commit_before>//! This module provides the functionality needed to convert diagnostics from
//! `cargo check` json format to the LSP diagnostic format.
use cargo_metadata::diagnostic::{
    Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
    DiagnosticSpanMacroExpansion,
};
use lsp_types::{
    Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
    NumberOrString, Position, Range, Url,
};
use std::{fmt::Write, path::PathBuf};

#[cfg(test)]
mod test;

/// Converts a Rust level string to a LSP severity
fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
    match val {
        DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
        DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
        DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
        DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
        DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
        DiagnosticLevel::Unknown => None,
    }
}

/// Check whether a file name is from macro invocation
fn is_from_macro(file_name: &str) -> bool {
    file_name.starts_with('<') && file_name.ends_with('>')
}

/// Converts a Rust macro span to a LSP location recursively
fn map_macro_span_to_location(
    span_macro: &DiagnosticSpanMacroExpansion,
    workspace_root: &PathBuf,
) -> Option<Location> {
    if !is_from_macro(&span_macro.span.file_name) {
        return Some(map_span_to_location(&span_macro.span, workspace_root));
    }

    if let Some(expansion) = &span_macro.span.expansion {
        return map_macro_span_to_location(&expansion, workspace_root);
    }

    None
}

/// Converts a Rust span to a LSP location
fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
    if is_from_macro(&span.file_name) && span.expansion.is_some() {
        let expansion = span.expansion.as_ref().unwrap();
        if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
            return macro_range;
        }
    }

    let mut file_name = workspace_root.clone();
    file_name.push(&span.file_name);
    let uri = Url::from_file_path(file_name).unwrap();

    let range = Range::new(
        Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
        Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
    );

    Location { uri, range }
}

/// Converts a secondary Rust span to a LSP related information
///
/// If the span is unlabelled this will return `None`.
fn map_secondary_span_to_related(
    span: &DiagnosticSpan,
    workspace_root: &PathBuf,
) -> Option<DiagnosticRelatedInformation> {
    if let Some(label) = &span.label {
        let location = map_span_to_location(span, workspace_root);
        Some(DiagnosticRelatedInformation { location, message: label.clone() })
    } else {
        // Nothing to label this with
        None
    }
}

/// Determines if diagnostic is related to unused code
fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
    if let Some(code) = &rd.code {
        match code.code.as_str() {
            "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
            | "unused_imports" | "unused_macros" | "unused_variables" => true,
            _ => false,
        }
    } else {
        false
    }
}

/// Determines if diagnostic is related to deprecated code
fn is_deprecated(rd: &RustDiagnostic) -> bool {
    if let Some(code) = &rd.code {
        match code.code.as_str() {
            "deprecated" => true,
            _ => false,
        }
    } else {
        false
    }
}

#[derive(Debug)]
pub struct SuggestedFix {
    pub title: String,
    pub location: Location,
    pub replacement: String,
    pub applicability: Applicability,
    pub diagnostics: Vec<Diagnostic>,
}

impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
    fn eq(&self, other: &SuggestedFix) -> bool {
        if self.title == other.title
            && self.location == other.location
            && self.replacement == other.replacement
        {
            // Applicability doesn't impl PartialEq...
            match (&self.applicability, &other.applicability) {
                (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
                (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
                (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
                (Applicability::Unspecified, Applicability::Unspecified) => true,
                _ => false,
            }
        } else {
            false
        }
    }
}

enum MappedRustChildDiagnostic {
    Related(DiagnosticRelatedInformation),
    SuggestedFix(SuggestedFix),
    MessageLine(String),
}

fn map_rust_child_diagnostic(
    rd: &RustDiagnostic,
    workspace_root: &PathBuf,
) -> MappedRustChildDiagnostic {
    let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
        Some(span) => span,
        None => {
            // `rustc` uses these spanless children as a way to print multi-line
            // messages
            return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
        }
    };

    // If we have a primary span use its location, otherwise use the parent
    let location = map_span_to_location(&span, workspace_root);

    if let Some(suggested_replacement) = &span.suggested_replacement {
        // Include our replacement in the title unless it's empty
        let title = if !suggested_replacement.is_empty() {
            format!("{}: '{}'", rd.message, suggested_replacement)
        } else {
            rd.message.clone()
        };

        MappedRustChildDiagnostic::SuggestedFix(SuggestedFix {
            title,
            location,
            replacement: suggested_replacement.clone(),
            applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown),
            diagnostics: vec![],
        })
    } else {
        MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
            location,
            message: rd.message.clone(),
        })
    }
}

#[derive(Debug)]
pub(crate) struct MappedRustDiagnostic {
    pub location: Location,
    pub diagnostic: Diagnostic,
    pub suggested_fixes: Vec<SuggestedFix>,
}

/// Converts a Rust root diagnostic to LSP form
///
/// This flattens the Rust diagnostic by:
///
/// 1. Creating a LSP diagnostic with the root message and primary span.
/// 2. Adding any labelled secondary spans to `relatedInformation`
/// 3. Categorising child diagnostics as either `SuggestedFix`es,
///    `relatedInformation` or additional message lines.
///
/// If the diagnostic has no primary span this will return `None`
pub(crate) fn map_rust_diagnostic_to_lsp(
    rd: &RustDiagnostic,
    workspace_root: &PathBuf,
) -> Option<MappedRustDiagnostic> {
    let primary_span = rd.spans.iter().find(|s| s.is_primary)?;

    let location = map_span_to_location(&primary_span, workspace_root);

    let severity = map_level_to_severity(rd.level);
    let mut primary_span_label = primary_span.label.as_ref();

    let mut source = String::from("rustc");
    let mut code = rd.code.as_ref().map(|c| c.code.clone());
    if let Some(code_val) = &code {
        // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
        let scoped_code: Vec<&str> = code_val.split("::").collect();
        if scoped_code.len() == 2 {
            source = String::from(scoped_code[0]);
            code = Some(String::from(scoped_code[1]));
        }
    }

    let mut related_information = vec![];
    let mut tags = vec![];

    for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
        let related = map_secondary_span_to_related(secondary_span, workspace_root);
        if let Some(related) = related {
            related_information.push(related);
        }
    }

    let mut suggested_fixes = vec![];
    let mut message = rd.message.clone();
    for child in &rd.children {
        let child = map_rust_child_diagnostic(&child, workspace_root);
        match child {
            MappedRustChildDiagnostic::Related(related) => related_information.push(related),
            MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => {
                suggested_fixes.push(suggested_fix)
            }
            MappedRustChildDiagnostic::MessageLine(message_line) => {
                write!(&mut message, "\n{}", message_line).unwrap();

                // These secondary messages usually duplicate the content of the
                // primary span label.
                primary_span_label = None;
            }
        }
    }

    if let Some(primary_span_label) = primary_span_label {
        write!(&mut message, "\n{}", primary_span_label).unwrap();
    }

    if is_unused_or_unnecessary(rd) {
        tags.push(DiagnosticTag::Unnecessary);
    }

    if is_deprecated(rd) {
        tags.push(DiagnosticTag::Deprecated);
    }

    let diagnostic = Diagnostic {
        range: location.range,
        severity,
        code: code.map(NumberOrString::String),
        source: Some(source),
        message,
        related_information: if !related_information.is_empty() {
            Some(related_information)
        } else {
            None
        },
        tags: if !tags.is_empty() { Some(tags) } else { None },
    };

    Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
}
<commit_msg>Resolve macro call site in more cases<commit_after>//! This module provides the functionality needed to convert diagnostics from
//! `cargo check` json format to the LSP diagnostic format.
use cargo_metadata::diagnostic::{
    Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
    DiagnosticSpanMacroExpansion,
};
use lsp_types::{
    Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
    NumberOrString, Position, Range, Url,
};
use std::{fmt::Write, path::PathBuf};

#[cfg(test)]
mod test;

/// Converts a Rust level string to a LSP severity
fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
    match val {
        DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
        DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
        DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
        DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
        DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
        DiagnosticLevel::Unknown => None,
    }
}

/// Check whether a file name is from macro invocation
fn is_from_macro(file_name: &str) -> bool {
    file_name.starts_with('<') && file_name.ends_with('>')
}

/// Converts a Rust macro span to a LSP location recursively
fn map_macro_span_to_location(
    span_macro: &DiagnosticSpanMacroExpansion,
    workspace_root: &PathBuf,
) -> Option<Location> {
    if !is_from_macro(&span_macro.span.file_name) {
        return Some(map_span_to_location(&span_macro.span, workspace_root));
    }

    if let Some(expansion) = &span_macro.span.expansion {
        return map_macro_span_to_location(&expansion, workspace_root);
    }

    None
}

/// Converts a Rust span to a LSP location
fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
    if span.expansion.is_some() {
        let expansion = span.expansion.as_ref().unwrap();
        if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
            return macro_range;
        }
    }

    let mut file_name = workspace_root.clone();
    file_name.push(&span.file_name);
    let uri = Url::from_file_path(file_name).unwrap();

    let range = Range::new(
        Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
        Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
    );

    Location { uri, range }
}

/// Converts a secondary Rust span to a LSP related information
///
/// If the span is unlabelled this will return `None`.
fn map_secondary_span_to_related(
    span: &DiagnosticSpan,
    workspace_root: &PathBuf,
) -> Option<DiagnosticRelatedInformation> {
    if let Some(label) = &span.label {
        let location = map_span_to_location(span, workspace_root);
        Some(DiagnosticRelatedInformation { location, message: label.clone() })
    } else {
        // Nothing to label this with
        None
    }
}

/// Determines if diagnostic is related to unused code
fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
    if let Some(code) = &rd.code {
        match code.code.as_str() {
            "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
            | "unused_imports" | "unused_macros" | "unused_variables" => true,
            _ => false,
        }
    } else {
        false
    }
}

/// Determines if diagnostic is related to deprecated code
fn is_deprecated(rd: &RustDiagnostic) -> bool {
    if let Some(code) = &rd.code {
        match code.code.as_str() {
            "deprecated" => true,
            _ => false,
        }
    } else {
        false
    }
}

#[derive(Debug)]
pub struct SuggestedFix {
    pub title: String,
    pub location: Location,
    pub replacement: String,
    pub applicability: Applicability,
    pub diagnostics: Vec<Diagnostic>,
}

impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
    fn eq(&self, other: &SuggestedFix) -> bool {
        if self.title == other.title
            && self.location == other.location
            && self.replacement == other.replacement
        {
            // Applicability doesn't impl PartialEq...
            match (&self.applicability, &other.applicability) {
                (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
                (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
                (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
                (Applicability::Unspecified, Applicability::Unspecified) => true,
                _ => false,
            }
        } else {
            false
        }
    }
}

enum MappedRustChildDiagnostic {
    Related(DiagnosticRelatedInformation),
    SuggestedFix(SuggestedFix),
    MessageLine(String),
}

fn map_rust_child_diagnostic(
    rd: &RustDiagnostic,
    workspace_root: &PathBuf,
) -> MappedRustChildDiagnostic {
    let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
        Some(span) => span,
        None => {
            // `rustc` uses these spanless children as a way to print multi-line
            // messages
            return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
        }
    };

    // If we have a primary span use its location, otherwise use the parent
    let location = map_span_to_location(&span, workspace_root);

    if let Some(suggested_replacement) = &span.suggested_replacement {
        // Include our replacement in the title unless it's empty
        let title = if !suggested_replacement.is_empty() {
            format!("{}: '{}'", rd.message, suggested_replacement)
        } else {
            rd.message.clone()
        };

        MappedRustChildDiagnostic::SuggestedFix(SuggestedFix {
            title,
            location,
            replacement: suggested_replacement.clone(),
            applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown),
            diagnostics: vec![],
        })
    } else {
        MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
            location,
            message: rd.message.clone(),
        })
    }
}

#[derive(Debug)]
pub(crate) struct MappedRustDiagnostic {
    pub location: Location,
    pub diagnostic: Diagnostic,
    pub suggested_fixes: Vec<SuggestedFix>,
}

/// Converts a Rust root diagnostic to LSP form
///
/// This flattens the Rust diagnostic by:
///
/// 1. Creating a LSP diagnostic with the root message and primary span.
/// 2. Adding any labelled secondary spans to `relatedInformation`
/// 3. Categorising child diagnostics as either `SuggestedFix`es,
///    `relatedInformation` or additional message lines.
///
/// If the diagnostic has no primary span this will return `None`
pub(crate) fn map_rust_diagnostic_to_lsp(
    rd: &RustDiagnostic,
    workspace_root: &PathBuf,
) -> Option<MappedRustDiagnostic> {
    let primary_span = rd.spans.iter().find(|s| s.is_primary)?;

    let location = map_span_to_location(&primary_span, workspace_root);

    let severity = map_level_to_severity(rd.level);
    let mut primary_span_label = primary_span.label.as_ref();

    let mut source = String::from("rustc");
    let mut code = rd.code.as_ref().map(|c| c.code.clone());
    if let Some(code_val) = &code {
        // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
        let scoped_code: Vec<&str> = code_val.split("::").collect();
        if scoped_code.len() == 2 {
            source = String::from(scoped_code[0]);
            code = Some(String::from(scoped_code[1]));
        }
    }

    let mut related_information = vec![];
    let mut tags = vec![];

    for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
        let related = map_secondary_span_to_related(secondary_span, workspace_root);
        if let Some(related) = related {
            related_information.push(related);
        }
    }

    let mut suggested_fixes = vec![];
    let mut message = rd.message.clone();
    for child in &rd.children {
        let child = map_rust_child_diagnostic(&child, workspace_root);
        match child {
            MappedRustChildDiagnostic::Related(related) => related_information.push(related),
            MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => {
                suggested_fixes.push(suggested_fix)
            }
            MappedRustChildDiagnostic::MessageLine(message_line) => {
                write!(&mut message, "\n{}", message_line).unwrap();

                // These secondary messages usually duplicate the content of the
                // primary span label.
                primary_span_label = None;
            }
        }
    }

    if let Some(primary_span_label) = primary_span_label {
        write!(&mut message, "\n{}", primary_span_label).unwrap();
    }

    if is_unused_or_unnecessary(rd) {
        tags.push(DiagnosticTag::Unnecessary);
    }

    if is_deprecated(rd) {
        tags.push(DiagnosticTag::Deprecated);
    }

    let diagnostic = Diagnostic {
        range: location.range,
        severity,
        code: code.map(NumberOrString::String),
        source: Some(source),
        message,
        related_information: if !related_information.is_empty() {
            Some(related_information)
        } else {
            None
        },
        tags: if !tags.is_empty() { Some(tags) } else { None },
    };

    Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
}
<|endoftext|>


<commit_before>use std::thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::sync::mpsc::{Sender,Receiver};
use std::collections::HashMap;
use consts::{WatchedEventType, ZkError};
use proto::WatchedEvent;
use zookeeper::{ZkResult, ZooKeeper};
use zookeeper_ext::ZooKeeperExt;
use watch;

pub type Data = HashMap<String, Arc<Vec<u8>>>;

#[derive(Debug)]
pub struct ChildData;

#[derive(Debug)]
pub enum PathChildrenCacheEvent {
    Initialized,
    ConnectionSuspended,
    ConnectionLost,
    ConnectionReconnected,
    ChildRemoved(ChildData),
    ChildAdded(ChildData),
    ChildUpdated(ChildData),
}

#[derive(Debug)]
pub enum RefreshMode {
    Standard,
    ForceGetDataAndStat,
}

pub enum Operation {
    Shutdown(Option<Sender<ZkResult<bool>>>),
    Refresh(RefreshMode, Option<Sender<ZkResult<()>>>),
    Event(PathChildrenCacheEvent, Option<Sender<ZkResult<bool>>>),
    GetData(String /* path */, Option<Sender<ZkResult<bool>>>),
}

use std::fmt;
impl fmt::Debug for Operation {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Operation::Shutdown(ref _sender) => {
                write!(f, "Operation::Shutdown")
            },
            Operation::Refresh(ref mode, ref _sender) => {
                write!(f, "Operation::Refresh({:?})", mode)
            },
            Operation::Event(ref ev, ref _sender) => {
                write!(f, "Operation::Event({:?})", ev)
            },
            Operation::GetData(ref path, ref _sender) => {
                write!(f, "Operation::GetData({})", path)
            }
        }
    }
}

pub struct PathChildrenCache {
    path: Arc<String>,
    zk: Arc<ZooKeeper>,
    data: Arc<Mutex<Data>>,
    worker_thread: Option<thread::JoinHandle<()>>,
    channel: Option<Sender<Operation>>,
}

impl PathChildrenCache {

    fn get_children(zk: Arc<ZooKeeper>, path: &str, data: Arc<Mutex<Data>>) -> ZkResult<()> {

        let zk1 = zk.clone();
        let data1 = data.clone();

        let watcher = move |event: &WatchedEvent| {
            match event.event_type {
                WatchedEventType::NodeChildrenChanged => {
                    let path = event.path.as_ref().expect("Path absent");
                    // Subscribe to new changes recursively
                    let _ = Self::get_children(zk1, path, data1); // ignore errors
                },
                _ => error!("Unexpected: {:?}", event)
            };
            // Send change event
        };

        let children = try!(zk.get_children_w(&path, watcher));

        let mut data_locked = data.lock().unwrap();

        for child in children.iter() {
            let child_path = join_path(path, child);

            if !data_locked.contains_key(&child_path) {

                let child_data = Self::get_data(zk.clone(), &child_path, data.clone());

                data_locked.insert(child_path, Arc::new(child_data));
            }
        }

        debug!("New data: {:?}", *data_locked);

        Ok(())
    }

    fn get_data(zk: Arc<ZooKeeper>, path: &str, data: Arc<Mutex<Data>>) -> Vec<u8> {
        let zk1 = zk.clone();
        let path1 = path.to_owned();

        let data_watcher = move |event: &WatchedEvent| {
            let mut data_locked = data.lock().unwrap();
            match event.event_type {
                WatchedEventType::NodeDeleted => {
                    data_locked.remove(&path1);
                },
                WatchedEventType::NodeDataChanged => {
                    // Subscribe to new changes recursively
                    let child_data = Self::get_data(zk1, &path1, data.clone());
                    data_locked.insert(path1, Arc::new(child_data));
                },
                _ => error!("Unexpected: {:?}", event)
            };

            debug!("New data: {:?}", *data_locked);

            //TODO Send change event
        };

        zk.get_data_w(path, data_watcher).unwrap().0
    }

    pub fn new(zk: Arc<ZooKeeper>, path: &str) -> ZkResult<PathChildrenCache> {

        let data = Arc::new(Mutex::new(HashMap::new()));

        try!(zk.ensure_path(path));

        Ok(PathChildrenCache{
            path: Arc::new(path.to_string()),
            zk: zk,
            data: data,
            worker_thread: None,
            channel: None,
        })
    }

    pub fn get_current_data(&self) -> Data {
        self.data.lock().unwrap().clone()
    }

    pub fn clear(&self) {
        self.data.lock().unwrap().clear()
    }

    pub fn start(&mut self) {
        let (tx, rx) = mpsc::channel();
        self.channel = Some(tx);

        let zk = self.zk.clone();
        let path = self.path.clone();
        let data = self.data.clone();

        self.worker_thread = Some(thread::spawn(move || {
            for op in rx.iter() {
                println!("handling op {:?}", op);
                match op {
                    Operation::Shutdown(maybe_chan) => {
                        println!("shutting down worker thread");
                        break;
                    },
                    Operation::Refresh(mode, maybe_chan) => {
                        println!("getting children");
                        let result = Self::get_children(zk.clone(), &*path, data.clone());
                        println!("got children {:?}", result);

                        maybe_chan.map_or((), |chan| {
                            chan.send(result);
                        });
                    },
                    Operation::GetData(path, maybe_chan) => {
                    },
                    Operation::Event(event, maybe_chan) => {
                    }
                }
            }
        }));

        self.offer_operation(Operation::Refresh(RefreshMode::ForceGetDataAndStat, None));
    }

    fn offer_operation(&self, op: Operation) -> ZkResult<()> {
        match self.channel {
            Some(ref chan) => {
                chan.send(op);
                Ok(())
            },
            None => Err(ZkError::APIError)
        }
    }

}

pub fn join_path(dir: &str, child: &str) -> String {
    let dir_bytes = dir.as_bytes();
    let mut result = dir.to_string();
    if dir_bytes[dir_bytes.len() - 1] != ('/' as u8) {
        result.push_str("/");
    }
    result.push_str(child);
    result
}
<commit_msg>recursively refresh by offering a new refresh operation if anything changes inside the watchers in get_children and get_data<commit_after>use std::thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::sync::mpsc::{Sender,Receiver};
use std::collections::HashMap;
use consts::{WatchedEventType, ZkError};
use proto::WatchedEvent;
use zookeeper::{ZkResult, ZooKeeper};
use zookeeper_ext::ZooKeeperExt;
use watch;

pub type Data = HashMap<String, Arc<Vec<u8>>>;

#[derive(Debug)]
pub struct ChildData;

#[derive(Debug)]
pub enum PathChildrenCacheEvent {
    Initialized,
    ConnectionSuspended,
    ConnectionLost,
    ConnectionReconnected,
    ChildRemoved(ChildData),
    ChildAdded(ChildData),
    ChildUpdated(ChildData),
}

#[derive(Debug)]
pub enum RefreshMode {
    Standard,
    ForceGetDataAndStat,
}

pub enum Operation {
    Shutdown(Option<Sender<ZkResult<bool>>>),
    Refresh(RefreshMode, Option<Sender<ZkResult<()>>>),
    Event(PathChildrenCacheEvent, Option<Sender<ZkResult<bool>>>),
    GetData(String /* path */, Option<Sender<ZkResult<bool>>>),
}

use std::fmt;
impl fmt::Debug for Operation {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Operation::Shutdown(ref _sender) => {
                write!(f, "Operation::Shutdown")
            },
            Operation::Refresh(ref mode, ref _sender) => {
                write!(f, "Operation::Refresh({:?})", mode)
            },
            Operation::Event(ref ev, ref _sender) => {
                write!(f, "Operation::Event({:?})", ev)
            },
            Operation::GetData(ref path, ref _sender) => {
                write!(f, "Operation::GetData({})", path)
            }
        }
    }
}

pub struct PathChildrenCache {
    path: Arc<String>,
    zk: Arc<ZooKeeper>,
    data: Arc<Mutex<Data>>,
    worker_thread: Option<thread::JoinHandle<()>>,
    channel: Option<Sender<Operation>>,
}

impl PathChildrenCache {

    fn get_children(zk: Arc<ZooKeeper>, path: &str, data: Arc<Mutex<Data>>, ops_chan: Sender<Operation>) -> ZkResult<()> {

        let zk1 = zk.clone();
        let data1 = data.clone();
        let ops_chan1 = ops_chan.clone();

        let watcher = move |event: &WatchedEvent| {
            match event.event_type {
                WatchedEventType::NodeChildrenChanged => {
                    let path = event.path.as_ref().expect("Path absent");

                    // Subscribe to new changes recursively
                    ops_chan1.send(Operation::Refresh(RefreshMode::Standard, None));
                },
                _ => error!("Unexpected: {:?}", event)
            };
            // Send change event
        };

        let children = try!(zk.get_children_w(&path, watcher));

        let mut data_locked = data.lock().unwrap();

        for child in children.iter() {
            let child_path = join_path(path, child);

            if !data_locked.contains_key(&child_path) {

                let child_data = Self::get_data(zk.clone(), &child_path, data.clone(), ops_chan.clone());

                data_locked.insert(child_path, Arc::new(child_data));
            }
        }

        debug!("New data: {:?}", *data_locked);

        Ok(())
    }

    fn get_data(zk: Arc<ZooKeeper>, path: &str, data: Arc<Mutex<Data>>, ops_chan: Sender<Operation>) -> Vec<u8> {
        let zk1 = zk.clone();
        let path1 = path.to_owned();

        let data_watcher = move |event: &WatchedEvent| {
            let mut data_locked = data.lock().unwrap();
            match event.event_type {
                WatchedEventType::NodeDeleted => {
                    data_locked.remove(&path1);
                },
                WatchedEventType::NodeDataChanged => {
                    // Subscribe to new changes recursively
                    ops_chan.send(Operation::GetData(path1, None));
                },
                _ => error!("Unexpected: {:?}", event)
            };

            debug!("New data: {:?}", *data_locked);

            //TODO Send change event
        };

        zk.get_data_w(path, data_watcher).unwrap().0
    }

    pub fn new(zk: Arc<ZooKeeper>, path: &str) -> ZkResult<PathChildrenCache> {

        let data = Arc::new(Mutex::new(HashMap::new()));

        try!(zk.ensure_path(path));

        Ok(PathChildrenCache{
            path: Arc::new(path.to_string()),
            zk: zk,
            data: data,
            worker_thread: None,
            channel: None,
        })
    }

    pub fn get_current_data(&self) -> Data {
        self.data.lock().unwrap().clone()
    }

    pub fn clear(&self) {
        self.data.lock().unwrap().clear()
    }

    pub fn start(&mut self) {
        let (tx, rx) = mpsc::channel();

        let zk = self.zk.clone();
        let path = self.path.clone();
        let data = self.data.clone();
        let ops_chan = tx.clone();

        self.channel = Some(tx);

        self.worker_thread = Some(thread::spawn(move || {
            for op in rx.iter() {
                println!("handling op {:?}", op);
                match op {
                    Operation::Shutdown(maybe_chan) => {
                        println!("shutting down worker thread");
                        break;
                    },
                    Operation::Refresh(mode, maybe_chan) => {
                        println!("getting children");
                        let result = Self::get_children(zk.clone(), &*path, data.clone(), ops_chan.clone());
                        println!("got children {:?}", result);

                        maybe_chan.map_or((), |chan| {
                            chan.send(result);
                        });
                    },
                    Operation::GetData(path, maybe_chan) => {
                    },
                    Operation::Event(event, maybe_chan) => {
                    }
                }
            }
        }));

        self.offer_operation(Operation::Refresh(RefreshMode::ForceGetDataAndStat, None));
    }

    fn offer_operation(&self, op: Operation) -> ZkResult<()> {
        match self.channel {
            Some(ref chan) => {
                chan.send(op);
                Ok(())
            },
            None => Err(ZkError::APIError)
        }
    }

}

pub fn join_path(dir: &str, child: &str) -> String {
    let dir_bytes = dir.as_bytes();
    let mut result = dir.to_string();
    if dir_bytes[dir_bytes.len() - 1] != ('/' as u8) {
        result.push_str("/");
    }
    result.push_str(child);
    result
}
<|endoftext|>


<commit_before>//! Render output types and related functions.

/// Output types.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub enum Type {
  Int(TypeChan),
  UInt(TypeChan),
  Float(TypeChan),
  Bool(TypeChan)
}

/// Output type channels. Can be 1, 2, 3 or 4 channels.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub enum TypeChan {
  One,
  Two,
  Three,
  Four
}

pub enum OutputList {
  Single(Type),
  Multi(Type, Box<OutputList>)
}

/// Associate an output type to a given type.
pub trait Output {
  const OUTPUT: Type;
}

/// Render outputs.
pub trait Outputs {
  /// Get the types of the output.
  fn ty() -> &'static [Type];
}

impl<A> Outputs for A where A: Output {
  fn ty() -> &'static [Type] {
    &[A::OUTPUT]
  }
}

macro_rules! multi_output_impl {
  ($($t:tt),*) => {
    impl<$($t),*> Outputs for ($($t),*) where $($t: Output),* {
      fn ty() -> &'static [Type] {
        &[$($t::OUTPUT),*]
      }
    }
  }
}

multi_output_impl!(A, B);
multi_output_impl!(A, B, C);
multi_output_impl!(A, B, C, D);
multi_output_impl!(A, B, C, D, E);
multi_output_impl!(A, B, C, D, E, F);
multi_output_impl!(A, B, C, D, E, F, G);
multi_output_impl!(A, B, C, D, E, F, G, H);
multi_output_impl!(A, B, C, D, E, F, G, H, I);
multi_output_impl!(A, B, C, D, E, F, G, H, I, J);
multi_output_impl!(A, B, C, D, E, F, G, H, I, J, K);
multi_output_impl!(A, B, C, D, E, F, G, H, I, J, K, L);

/// One-dimensional integral output a.k.a. red channel.
pub struct RI;

impl Output for RI {
  const OUTPUT: Type = Type::Int(TypeChan::One);
}

/// Two dimensional integral output a.k.a. red-green channels.
pub struct RGI;

impl Output for RGI {
  const OUTPUT: Type = Type::Int(TypeChan::Two);
}

/// Three dimensional integral output a.k.a. red-green-blue channels.
pub struct RGBI;

impl Output for RGBI {
  const OUTPUT: Type = Type::Int(TypeChan::Three);
}

/// Four dimensional integral output a.k.a. red-green-blue-alpha channels.
pub struct RGBAI;

impl Output for RGBAI {
  const OUTPUT: Type = Type::Int(TypeChan::Four);
}

/// One-dimensional unigned integral output a.k.a. red channel.
pub struct RU;

impl Output for RU {
  const OUTPUT: Type = Type::Int(TypeChan::One);
}

/// Two dimensional unsigned integral output a.k.a. red-green channels.
pub struct RGU;

impl Output for RGU {
  const OUTPUT: Type = Type::Int(TypeChan::Two);
}

/// Three dimensional unsigned integral output a.k.a. red-green-blue channels.
pub struct RGBU;

impl Output for RGBU {
  const OUTPUT: Type = Type::Int(TypeChan::Three);
}

/// Four dimensional unsigned integral output a.k.a. red-green-blue-alpha channels.
pub struct RGBAU;

impl Output for RGBAU {
  const OUTPUT: Type = Type::Int(TypeChan::Four);
}

/// One-dimensional floating output a.k.a. red channel.
pub struct RF;

impl Output for RF {
  const OUTPUT: Type = Type::Float(TypeChan::One);
}

/// Two dimensional floating output a.k.a. red-green channels.
pub struct RGF;

impl Output for RGF {
  const OUTPUT: Type = Type::Float(TypeChan::Two);
}

/// Three dimensional floating output a.k.a. red-green-blue channels.
pub struct RGBF;

impl Output for RGBF {
  const OUTPUT: Type = Type::Float(TypeChan::Three);
}

/// Four dimensional floating output a.k.a. red-green-blue-alpha channels.
pub struct RGBAF;

impl Output for RGBAF {
  const OUTPUT: Type = Type::Float(TypeChan::Four);
}

/// One-dimensional boolean output a.k.a. red channel.
pub struct RZ;

impl Output for RZ {
  const OUTPUT: Type = Type::Float(TypeChan::One);
}

/// Two dimensional boolean output a.k.a. red-green channels.
pub struct RGZ;

impl Output for RGZ {
  const OUTPUT: Type = Type::Float(TypeChan::Two);
}

/// Three dimensional boolean output a.k.a. red-green-blue channels.
pub struct RGBZ;

impl Output for RGBZ {
  const OUTPUT: Type = Type::Float(TypeChan::Three);
}

/// Four dimensional boolean output a.k.a. red-green-blue-alpha channels.
pub struct RGBAZ;

impl Output for RGBAZ {
  const OUTPUT: Type = Type::Float(TypeChan::Four);
}
<commit_msg>Remove useless type.<commit_after>//! Render output types and related functions.

/// Output types.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub enum Type {
  Int(TypeChan),
  UInt(TypeChan),
  Float(TypeChan),
  Bool(TypeChan)
}

/// Output type channels. Can be 1, 2, 3 or 4 channels.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub enum TypeChan {
  One,
  Two,
  Three,
  Four
}

/// Associate an output type to a given type.
pub trait Output {
  const OUTPUT: Type;
}

/// Render outputs.
pub trait Outputs {
  /// Get the types of the output.
  fn ty() -> &'static [Type];
}

impl<A> Outputs for A where A: Output {
  fn ty() -> &'static [Type] {
    &[A::OUTPUT]
  }
}

macro_rules! multi_output_impl {
  ($($t:tt),*) => {
    impl<$($t),*> Outputs for ($($t),*) where $($t: Output),* {
      fn ty() -> &'static [Type] {
        &[$($t::OUTPUT),*]
      }
    }
  }
}

multi_output_impl!(A, B);
multi_output_impl!(A, B, C);
multi_output_impl!(A, B, C, D);
multi_output_impl!(A, B, C, D, E);
multi_output_impl!(A, B, C, D, E, F);
multi_output_impl!(A, B, C, D, E, F, G);
multi_output_impl!(A, B, C, D, E, F, G, H);
multi_output_impl!(A, B, C, D, E, F, G, H, I);
multi_output_impl!(A, B, C, D, E, F, G, H, I, J);
multi_output_impl!(A, B, C, D, E, F, G, H, I, J, K);
multi_output_impl!(A, B, C, D, E, F, G, H, I, J, K, L);

/// One-dimensional integral output a.k.a. red channel.
pub struct RI;

impl Output for RI {
  const OUTPUT: Type = Type::Int(TypeChan::One);
}

/// Two dimensional integral output a.k.a. red-green channels.
pub struct RGI;

impl Output for RGI {
  const OUTPUT: Type = Type::Int(TypeChan::Two);
}

/// Three dimensional integral output a.k.a. red-green-blue channels.
pub struct RGBI;

impl Output for RGBI {
  const OUTPUT: Type = Type::Int(TypeChan::Three);
}

/// Four dimensional integral output a.k.a. red-green-blue-alpha channels.
pub struct RGBAI;

impl Output for RGBAI {
  const OUTPUT: Type = Type::Int(TypeChan::Four);
}

/// One-dimensional unigned integral output a.k.a. red channel.
pub struct RU;

impl Output for RU {
  const OUTPUT: Type = Type::Int(TypeChan::One);
}

/// Two dimensional unsigned integral output a.k.a. red-green channels.
pub struct RGU;

impl Output for RGU {
  const OUTPUT: Type = Type::Int(TypeChan::Two);
}

/// Three dimensional unsigned integral output a.k.a. red-green-blue channels.
pub struct RGBU;

impl Output for RGBU {
  const OUTPUT: Type = Type::Int(TypeChan::Three);
}

/// Four dimensional unsigned integral output a.k.a. red-green-blue-alpha channels.
pub struct RGBAU;

impl Output for RGBAU {
  const OUTPUT: Type = Type::Int(TypeChan::Four);
}

/// One-dimensional floating output a.k.a. red channel.
pub struct RF;

impl Output for RF {
  const OUTPUT: Type = Type::Float(TypeChan::One);
}

/// Two dimensional floating output a.k.a. red-green channels.
pub struct RGF;

impl Output for RGF {
  const OUTPUT: Type = Type::Float(TypeChan::Two);
}

/// Three dimensional floating output a.k.a. red-green-blue channels.
pub struct RGBF;

impl Output for RGBF {
  const OUTPUT: Type = Type::Float(TypeChan::Three);
}

/// Four dimensional floating output a.k.a. red-green-blue-alpha channels.
pub struct RGBAF;

impl Output for RGBAF {
  const OUTPUT: Type = Type::Float(TypeChan::Four);
}

/// One-dimensional boolean output a.k.a. red channel.
pub struct RZ;

impl Output for RZ {
  const OUTPUT: Type = Type::Float(TypeChan::One);
}

/// Two dimensional boolean output a.k.a. red-green channels.
pub struct RGZ;

impl Output for RGZ {
  const OUTPUT: Type = Type::Float(TypeChan::Two);
}

/// Three dimensional boolean output a.k.a. red-green-blue channels.
pub struct RGBZ;

impl Output for RGBZ {
  const OUTPUT: Type = Type::Float(TypeChan::Three);
}

/// Four dimensional boolean output a.k.a. red-green-blue-alpha channels.
pub struct RGBAZ;

impl Output for RGBAZ {
  const OUTPUT: Type = Type::Float(TypeChan::Four);
}
<|endoftext|>