<commit_before>use monolith::html::{html_to_dom, stringify_document, walk_and_embed_assets};
use monolith::utils::{data_url_to_data, is_data_url, is_file_url, is_http_url, retrieve_asset};
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use reqwest::Url;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::{self, Error, Write};
use std::path::Path;
use std::process;
use std::time::Duration;

mod args;
mod macros;

#[macro_use]
extern crate clap;
use crate::args::AppArgs;

enum Output {
    Stdout(io::Stdout),
    File(fs::File),
}

impl Output {
    fn new(file_path: &str) -> Result<Output, Error> {
        if file_path.is_empty() {
            Ok(Output::Stdout(io::stdout()))
        } else {
            Ok(Output::File(fs::File::create(file_path)?))
        }
    }

    fn writeln_str(&mut self, s: &str) -> Result<(), Error> {
        match self {
            Output::Stdout(stdout) => {
                writeln!(stdout, "{}", s)?;
                stdout.flush()
            }
            Output::File(f) => {
                writeln!(f, "{}", s)?;
                f.flush()
            }
        }
    }
}

fn main() {
    let app_args = AppArgs::get();
    let original_target: &str = &app_args.target;
    let target_url: &str;
    let base_url;
    let dom;

    // Pre-process the input
    let cwd_normalized: String =
        str!(env::current_dir().unwrap().to_str().unwrap()).replace("\\", "/");
    let path = Path::new(original_target);
    let mut target: String = str!(original_target.clone()).replace("\\", "/");
    let path_is_relative: bool = path.is_relative();

    if target.clone().len() == 0 {
        eprintln!("No target specified");
        process::exit(1);
    } else if is_http_url(target.clone()) || is_data_url(target.clone()) {
        target_url = target.as_str();
    } else if is_file_url(target.clone()) {
        target_url = target.as_str();
    } else if path.exists() {
        if !path.is_file() {
            eprintln!("Local target is not a file: {}", original_target);
            process::exit(1);
        }
        target.insert_str(0, if cfg!(windows) { "file:///" } else { "file://" });
        if path_is_relative {
            target.insert_str(if cfg!(windows) { 8 } else { 7 }, &cwd_normalized);
            target.insert_str(
                if cfg!(windows) { 8 } else { 7 } + &cwd_normalized.len(),
                "/",
            );
        }
        target_url = target.as_str();
    } else {
        target.insert_str(0, "http://");
        target_url = target.as_str();
    }

    let mut output = Output::new(&app_args.output).expect("Could not prepare output");

    // Initialize client
    let mut cache = HashMap::new();
    let mut header_map = HeaderMap::new();
    header_map.insert(
        USER_AGENT,
        HeaderValue::from_str(&app_args.user_agent).expect("Invalid User-Agent header specified"),
    );

    let timeout: u64 = if app_args.timeout > 0 {
        app_args.timeout
    } else {
        std::u64::MAX / 4
    };
    let client = Client::builder()
        .timeout(Duration::from_secs(timeout))
        .danger_accept_invalid_certs(app_args.insecure)
        .default_headers(header_map)
        .build()
        .expect("Failed to initialize HTTP client");

    // Retrieve root document
    if is_file_url(target_url) || is_http_url(target_url) {
        match retrieve_asset(&mut cache, &client, target_url, target_url, app_args.silent) {
            Ok((data, final_url, _media_type)) => {
                base_url = final_url;
                dom = html_to_dom(&String::from_utf8_lossy(&data));
            }
            Err(_) => {
                eprintln!("Could not retrieve target document");
                process::exit(1);
            }
        }
    } else if is_data_url(target_url) {
        let (media_type, data): (String, Vec<u8>) = data_url_to_data(target_url);
        if !media_type.eq_ignore_ascii_case("text/html") {
            eprintln!("Unsupported data URL media type");
            process::exit(1);
        }
        base_url = str!(target_url);
        dom = html_to_dom(&String::from_utf8_lossy(&data));
    } else {
        process::exit(1);
    }

    let time_saved = time::now_utc();

    walk_and_embed_assets(
        &mut cache,
        &client,
        &base_url,
        &dom.document,
        app_args.no_css,
        app_args.no_fonts,
        app_args.no_frames,
        app_args.no_js,
        app_args.no_images,
        app_args.silent,
    );

    let mut html: String = stringify_document(
        &dom.document,
        app_args.no_css,
        app_args.no_frames,
        app_args.no_js,
        app_args.no_images,
        app_args.isolate,
    );

    if !app_args.no_metadata {
        // Safe to unwrap (we just put this through an HTTP request)
        let mut clean_url = Url::parse(&base_url).unwrap();
        clean_url.set_fragment(None);
        // Don't include credentials
        clean_url.set_username("").unwrap();
        clean_url.set_password(None).unwrap();
        let metadata_comment = if is_http_url(&base_url) {
            format!(
                "<!-- Saved from {} at {} using {} v{} -->\n",
                &clean_url,
                time_saved.rfc3339(),
                env!("CARGO_PKG_NAME"),
                env!("CARGO_PKG_VERSION"),
            )
        } else {
            format!(
                "<!-- Saved from local source at {} using {} v{} -->\n",
                time_saved.rfc3339(),
                env!("CARGO_PKG_NAME"),
                env!("CARGO_PKG_VERSION"),
            )
        };
        html.insert_str(0, &metadata_comment);
    }

    output
        .writeln_str(&html)
        .expect("Could not write HTML output");
}
<commit_msg>only attempt to remove credentals from HTTP(S) URLs<commit_after>use monolith::html::{html_to_dom, stringify_document, walk_and_embed_assets};
use monolith::utils::{data_url_to_data, is_data_url, is_file_url, is_http_url, retrieve_asset};
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use reqwest::Url;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::{self, Error, Write};
use std::path::Path;
use std::process;
use std::time::Duration;

mod args;
mod macros;

#[macro_use]
extern crate clap;
use crate::args::AppArgs;

enum Output {
    Stdout(io::Stdout),
    File(fs::File),
}

impl Output {
    fn new(file_path: &str) -> Result<Output, Error> {
        if file_path.is_empty() {
            Ok(Output::Stdout(io::stdout()))
        } else {
            Ok(Output::File(fs::File::create(file_path)?))
        }
    }

    fn writeln_str(&mut self, s: &str) -> Result<(), Error> {
        match self {
            Output::Stdout(stdout) => {
                writeln!(stdout, "{}", s)?;
                stdout.flush()
            }
            Output::File(f) => {
                writeln!(f, "{}", s)?;
                f.flush()
            }
        }
    }
}

fn main() {
    let app_args = AppArgs::get();
    let original_target: &str = &app_args.target;
    let target_url: &str;
    let base_url;
    let dom;

    // Pre-process the input
    let cwd_normalized: String =
        str!(env::current_dir().unwrap().to_str().unwrap()).replace("\\", "/");
    let path = Path::new(original_target);
    let mut target: String = str!(original_target.clone()).replace("\\", "/");
    let path_is_relative: bool = path.is_relative();

    if target.clone().len() == 0 {
        eprintln!("No target specified");
        process::exit(1);
    } else if is_http_url(target.clone()) || is_data_url(target.clone()) {
        target_url = target.as_str();
    } else if is_file_url(target.clone()) {
        target_url = target.as_str();
    } else if path.exists() {
        if !path.is_file() {
            eprintln!("Local target is not a file: {}", original_target);
            process::exit(1);
        }
        target.insert_str(0, if cfg!(windows) { "file:///" } else { "file://" });
        if path_is_relative {
            target.insert_str(if cfg!(windows) { 8 } else { 7 }, &cwd_normalized);
            target.insert_str(
                if cfg!(windows) { 8 } else { 7 } + &cwd_normalized.len(),
                "/",
            );
        }
        target_url = target.as_str();
    } else {
        target.insert_str(0, "http://");
        target_url = target.as_str();
    }

    let mut output = Output::new(&app_args.output).expect("Could not prepare output");

    // Initialize client
    let mut cache = HashMap::new();
    let mut header_map = HeaderMap::new();
    header_map.insert(
        USER_AGENT,
        HeaderValue::from_str(&app_args.user_agent).expect("Invalid User-Agent header specified"),
    );

    let timeout: u64 = if app_args.timeout > 0 {
        app_args.timeout
    } else {
        std::u64::MAX / 4
    };
    let client = Client::builder()
        .timeout(Duration::from_secs(timeout))
        .danger_accept_invalid_certs(app_args.insecure)
        .default_headers(header_map)
        .build()
        .expect("Failed to initialize HTTP client");

    // Retrieve root document
    if is_file_url(target_url) || is_http_url(target_url) {
        match retrieve_asset(&mut cache, &client, target_url, target_url, app_args.silent) {
            Ok((data, final_url, _media_type)) => {
                base_url = final_url;
                dom = html_to_dom(&String::from_utf8_lossy(&data));
            }
            Err(_) => {
                eprintln!("Could not retrieve target document");
                process::exit(1);
            }
        }
    } else if is_data_url(target_url) {
        let (media_type, data): (String, Vec<u8>) = data_url_to_data(target_url);
        if !media_type.eq_ignore_ascii_case("text/html") {
            eprintln!("Unsupported data URL media type");
            process::exit(1);
        }
        base_url = str!(target_url);
        dom = html_to_dom(&String::from_utf8_lossy(&data));
    } else {
        process::exit(1);
    }

    let time_saved = time::now_utc();

    walk_and_embed_assets(
        &mut cache,
        &client,
        &base_url,
        &dom.document,
        app_args.no_css,
        app_args.no_fonts,
        app_args.no_frames,
        app_args.no_js,
        app_args.no_images,
        app_args.silent,
    );

    let mut html: String = stringify_document(
        &dom.document,
        app_args.no_css,
        app_args.no_frames,
        app_args.no_js,
        app_args.no_images,
        app_args.isolate,
    );

    if !app_args.no_metadata {
        // Safe to unwrap (we just put this through an HTTP request)
        let mut clean_url = Url::parse(&base_url).unwrap();
        clean_url.set_fragment(None);
        // Prevent credentials from getting into metadata
        if is_http_url(&base_url) {
            // Only HTTP(S) URLs may feature credentials
            clean_url.set_username("").unwrap();
            clean_url.set_password(None).unwrap();
        }
        let metadata_comment = if is_http_url(&base_url) {
            format!(
                "<!-- Saved from {} at {} using {} v{} -->\n",
                &clean_url,
                time_saved.rfc3339(),
                env!("CARGO_PKG_NAME"),
                env!("CARGO_PKG_VERSION"),
            )
        } else {
            format!(
                "<!-- Saved from local source at {} using {} v{} -->\n",
                time_saved.rfc3339(),
                env!("CARGO_PKG_NAME"),
                env!("CARGO_PKG_VERSION"),
            )
        };
        html.insert_str(0, &metadata_comment);
    }

    output
        .writeln_str(&html)
        .expect("Could not write HTML output");
}
<|endoftext|>


<commit_before>use std::env;
use std::ffi::OsString;
use std::fmt::{self, Display};
use std::fs::{self, File};
use std::io::{self, BufRead, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};

use atty::Stream::{Stderr, Stdout};
use prettyprint::{PagingMode, PrettyPrinter, PrettyPrintError};
use quote::quote;
use structopt::clap::AppSettings;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
enum Opts {
    /// Show the result of macro expansion.
    #[structopt(
        name = "expand",
        raw(
            setting = "AppSettings::UnifiedHelpMessage",
            setting = "AppSettings::DeriveDisplayOrder",
            setting = "AppSettings::DontCollapseArgsInUsage"
        )
    )]
    Expand(Args),
}

#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
struct Args {
    /// Space-separated list of features to activate
    #[structopt(long, value_name = "FEATURES")]
    features: Option<String>,

    /// Activate all available features
    #[structopt(long)]
    all_features: bool,

    /// Do not activate the `default` feature
    #[structopt(long)]
    no_default_features: bool,

    /// Build only this package's library
    #[structopt(long)]
    lib: bool,

    /// Build only the specified binary
    #[structopt(long, value_name = "NAME")]
    bin: Option<String>,

    /// Build only the specified example
    #[structopt(long, value_name = "NAME")]
    example: Option<String>,

    /// Build only the specified test target
    #[structopt(long, value_name = "NAME")]
    test: Option<String>,

    /// Build only the specified bench target
    #[structopt(long, value_name = "NAME")]
    bench: Option<String>,

    /// Target triple which compiles will be for
    #[structopt(long, value_name = "TARGET")]
    target: Option<String>,

    /// Directory for all generated artifacts
    #[structopt(long, value_name = "DIRECTORY", parse(from_os_str))]
    target_dir: Option<PathBuf>,

    /// Path to Cargo.toml
    #[structopt(long, value_name = "PATH", parse(from_os_str))]
    manifest_path: Option<PathBuf>,

    /// Number of parallel jobs, defaults to # of CPUs
    #[structopt(short, long, value_name = "N")]
    jobs: Option<u64>,

    /// Coloring: auto, always, never
    #[structopt(long, value_name = "WHEN")]
    color: Option<String>,

    /// Require Cargo.lock and cache are up to date
    #[structopt(long)]
    frozen: bool,

    /// Require Cargo.lock is up to date
    #[structopt(long)]
    locked: bool,

    /// Unstable (nightly-only) flags to Cargo
    #[structopt(short = "Z", value_name = "FLAG")]
    unstable_flags: Vec<String>,
}

fn main() {
    let result = cargo_expand_or_run_nightly();
    process::exit(match result {
        Ok(code) => code,
        Err(err) => {
            let _ = writeln!(&mut io::stderr(), "{}", err);
            1
        }
    });
}

enum Error {
    Io(io::Error),
    Print(PrettyPrintError),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Error::Io(error)
    }
}

impl From<PrettyPrintError> for Error {
    fn from(error: PrettyPrintError) -> Self {
        Error::Print(error)
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(error) => Display::fmt(error, formatter),
            Error::Print(error) => Display::fmt(error, formatter),
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

fn cargo_expand_or_run_nightly() -> Result<i32> {
    const NO_RUN_NIGHTLY: &str = "CARGO_EXPAND_NO_RUN_NIGHTLY";

    let maybe_nightly = !definitely_not_nightly();
    if maybe_nightly || env::var_os(NO_RUN_NIGHTLY).is_some() {
        return cargo_expand();
    }

    let mut nightly = Command::new("cargo");
    nightly.arg("+nightly");
    nightly.arg("expand");

    let mut args = env::args_os().peekable();
    args.next().unwrap(); // cargo
    if args.peek().map_or(false, |arg| arg == "expand") {
        args.next().unwrap(); // expand
    }
    nightly.args(args);

    // Hopefully prevent infinite re-run loop.
    nightly.env(NO_RUN_NIGHTLY, "");

    let status = nightly.status()?;

    Ok(match status.code() {
        Some(code) => code,
        None => {
            if status.success() {
                0
            } else {
                1
            }
        }
    })
}

fn definitely_not_nightly() -> bool {
    let mut cmd = Command::new(cargo_binary());
    cmd.arg("--version");

    let output = match cmd.output() {
        Ok(output) => output,
        Err(_) => return false,
    };

    let version = match String::from_utf8(output.stdout) {
        Ok(version) => version,
        Err(_) => return false,
    };

    version.starts_with("cargo 1") && !version.contains("nightly")
}

fn cargo_binary() -> OsString {
    env::var_os("CARGO").unwrap_or_else(|| "cargo".to_owned().into())
}

fn cargo_expand() -> Result<i32> {
    let Opts::Expand(args) = Opts::from_args();

    let mut builder = tempfile::Builder::new();
    builder.prefix("cargo-expand");
    let outdir = builder.tempdir().expect("failed to create tmp file");
    let outfile_path = outdir.path().join("expanded");

    // Run cargo
    let mut cmd = Command::new(cargo_binary());
    apply_args(&mut cmd, &args, &outfile_path);
    let code = filter_err(&mut cmd, ignore_cargo_err)?;

    let mut outfile = File::open(&outfile_path)?;
    if outfile.metadata()?.len() == 0 {
        let _ = writeln!(
            &mut io::stderr(),
            "ERROR: rustc produced no expanded output"
        );
        return Ok(if code == 0 { 1 } else { code });
    }

    // Run rustfmt
    let which_rustfmt = which(&["rustfmt"]);
    if let Some(fmt) = which_rustfmt {
        // Discard comments, which are misplaced by the compiler
        let mut content = Vec::new();
        outfile.read_to_end(&mut content)?;
        match String::from_utf8(content) {
            Ok(content) => {
                if let Ok(syntax_tree) = syn::parse_file(&content) {
                    let content = quote!(#syntax_tree).to_string();
                    fs::write(&outfile_path, content)?;
                }
            }
            Err(_) => {
                let _ = writeln!(&mut io::stderr(), "WARNING: non-UTF8 content");
            }
        }

        // Ignore any errors.
        let _status = Command::new(fmt)
            .arg(&outfile_path)
            .stderr(Stdio::null())
            .status();
    }

    // Run pretty printer
    let do_color = match args.color.as_ref().map(String::as_str) {
        Some("always") => true,
        Some("never") => false,
        None | Some("auto") | Some(_) => atty::is(Stdout),
    };
    if do_color {
        let mut content = fs::read_to_string(&outfile_path)?;
        if content.ends_with('\n') {
            // Pretty printer seems to print an extra trailing newline.
            content.truncate(content.len() - 1);
        }
        let printer = PrettyPrinter::default()
            .header(false)
            .grid(false)
            .line_numbers(false)
            .language("rust")
            .paging_mode(PagingMode::Never)
            .build()
            .unwrap();
        printer.string(content)?;
    } else {
        let mut reader = File::open(&outfile_path)?;
        io::copy(&mut reader, &mut io::stdout())?;
    }

    Ok(0)
}

// Based on https://github.com/rsolomo/cargo-check
fn apply_args(cmd: &mut Command, args: &Args, outfile: &Path) {
    cmd.arg("rustc");
    cmd.arg("--profile=check");

    if let Some(features) = &args.features {
        cmd.arg("--features");
        cmd.arg(features);
    }

    if args.all_features {
        cmd.arg("--all-features");
    }

    if args.no_default_features {
        cmd.arg("--no-default-features");
    }

    if args.lib {
        cmd.arg("--lib");
    }

    if let Some(bin) = &args.bin {
        cmd.arg("--bin");
        cmd.arg(bin);
    }

    if let Some(example) = &args.example {
        cmd.arg("--example");
        cmd.arg(example);
    }

    if let Some(test) = &args.test {
        cmd.arg("--test");
        cmd.arg(test);
    }

    if let Some(bench) = &args.bench {
        cmd.arg("--bench");
        cmd.arg(bench);
    }

    if let Some(target) = &args.target {
        cmd.arg("--target");
        cmd.arg(target);
    }

    if let Some(target_dir) = &args.target_dir {
        cmd.arg("--target-dir");
        cmd.arg(target_dir);
    }

    if let Some(manifest_path) = &args.manifest_path {
        cmd.arg("--manifest-path");
        cmd.arg(manifest_path);
    }

    if let Some(jobs) = args.jobs {
        cmd.arg("--jobs");
        cmd.arg(jobs.to_string());
    }

    cmd.arg("--color");
    if let Some(color) = &args.color {
        cmd.arg(color);
    } else {
        cmd.arg(if atty::is(Stderr) { "always" } else { "never" });
    }

    if args.frozen {
        cmd.arg("--frozen");
    }

    if args.locked {
        cmd.arg("--locked");
    }

    for unstable_flag in &args.unstable_flags {
        cmd.arg("-Z");
        cmd.arg(unstable_flag);
    }

    cmd.arg("--");
    cmd.arg("-o");
    cmd.arg(outfile);
    cmd.arg("-Zunstable-options");
    cmd.arg("--pretty=expanded");
}

fn which(cmd: &[&str]) -> Option<OsString> {
    if env::args_os().any(|arg| arg == *"--help") {
        return None;
    }

    if let Some(which) = env::var_os(&cmd[0].to_uppercase()) {
        return if which.is_empty() { None } else { Some(which) };
    }

    let spawn = Command::new(cmd[0])
        .args(&cmd[1..])
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn();
    let mut child = match spawn {
        Ok(child) => child,
        Err(_) => {
            return None;
        }
    };

    let exit = match child.wait() {
        Ok(exit) => exit,
        Err(_) => {
            return None;
        }
    };

    if exit.success() {
        Some(cmd[0].into())
    } else {
        None
    }
}

fn filter_err(cmd: &mut Command, ignore: fn(&str) -> bool) -> io::Result<i32> {
    let mut child = cmd.stderr(Stdio::piped()).spawn()?;
    let mut stderr = io::BufReader::new(child.stderr.take().unwrap());
    let mut line = String::new();
    while let Ok(n) = stderr.read_line(&mut line) {
        if n == 0 {
            break;
        }
        if !ignore(&line) {
            let _ = write!(&mut io::stderr(), "{}", line);
        }
        line.clear();
    }
    let code = child.wait()?.code().unwrap_or(1);
    Ok(code)
}

fn ignore_cargo_err(line: &str) -> bool {
    if line.trim().is_empty() {
        return true;
    }

    let blacklist = [
        "ignoring specified output filename because multiple outputs were \
         requested",
        "ignoring specified output filename for 'link' output because multiple \
         outputs were requested",
        "ignoring --out-dir flag due to -o flag",
        "ignoring -C extra-filename flag due to -o flag",
        "due to multiple output types requested, the explicitly specified \
         output file name will be adapted for each output type",
    ];
    for s in &blacklist {
        if line.contains(s) {
            return true;
        }
    }

    false
}
<commit_msg>Ignore broken pipe errors from printer<commit_after>use std::env;
use std::ffi::OsString;
use std::fmt::{self, Display};
use std::fs::{self, File};
use std::io::{self, BufRead, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};

use atty::Stream::{Stderr, Stdout};
use prettyprint::{PagingMode, PrettyPrinter, PrettyPrintError};
use quote::quote;
use structopt::clap::AppSettings;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
enum Opts {
    /// Show the result of macro expansion.
    #[structopt(
        name = "expand",
        raw(
            setting = "AppSettings::UnifiedHelpMessage",
            setting = "AppSettings::DeriveDisplayOrder",
            setting = "AppSettings::DontCollapseArgsInUsage"
        )
    )]
    Expand(Args),
}

#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
struct Args {
    /// Space-separated list of features to activate
    #[structopt(long, value_name = "FEATURES")]
    features: Option<String>,

    /// Activate all available features
    #[structopt(long)]
    all_features: bool,

    /// Do not activate the `default` feature
    #[structopt(long)]
    no_default_features: bool,

    /// Build only this package's library
    #[structopt(long)]
    lib: bool,

    /// Build only the specified binary
    #[structopt(long, value_name = "NAME")]
    bin: Option<String>,

    /// Build only the specified example
    #[structopt(long, value_name = "NAME")]
    example: Option<String>,

    /// Build only the specified test target
    #[structopt(long, value_name = "NAME")]
    test: Option<String>,

    /// Build only the specified bench target
    #[structopt(long, value_name = "NAME")]
    bench: Option<String>,

    /// Target triple which compiles will be for
    #[structopt(long, value_name = "TARGET")]
    target: Option<String>,

    /// Directory for all generated artifacts
    #[structopt(long, value_name = "DIRECTORY", parse(from_os_str))]
    target_dir: Option<PathBuf>,

    /// Path to Cargo.toml
    #[structopt(long, value_name = "PATH", parse(from_os_str))]
    manifest_path: Option<PathBuf>,

    /// Number of parallel jobs, defaults to # of CPUs
    #[structopt(short, long, value_name = "N")]
    jobs: Option<u64>,

    /// Coloring: auto, always, never
    #[structopt(long, value_name = "WHEN")]
    color: Option<String>,

    /// Require Cargo.lock and cache are up to date
    #[structopt(long)]
    frozen: bool,

    /// Require Cargo.lock is up to date
    #[structopt(long)]
    locked: bool,

    /// Unstable (nightly-only) flags to Cargo
    #[structopt(short = "Z", value_name = "FLAG")]
    unstable_flags: Vec<String>,
}

fn main() {
    let result = cargo_expand_or_run_nightly();
    process::exit(match result {
        Ok(code) => code,
        Err(err) => {
            let _ = writeln!(&mut io::stderr(), "{}", err);
            1
        }
    });
}

enum Error {
    Io(io::Error),
    Print(PrettyPrintError),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Error::Io(error)
    }
}

impl From<PrettyPrintError> for Error {
    fn from(error: PrettyPrintError) -> Self {
        Error::Print(error)
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(error) => Display::fmt(error, formatter),
            Error::Print(error) => Display::fmt(error, formatter),
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

fn cargo_expand_or_run_nightly() -> Result<i32> {
    const NO_RUN_NIGHTLY: &str = "CARGO_EXPAND_NO_RUN_NIGHTLY";

    let maybe_nightly = !definitely_not_nightly();
    if maybe_nightly || env::var_os(NO_RUN_NIGHTLY).is_some() {
        return cargo_expand();
    }

    let mut nightly = Command::new("cargo");
    nightly.arg("+nightly");
    nightly.arg("expand");

    let mut args = env::args_os().peekable();
    args.next().unwrap(); // cargo
    if args.peek().map_or(false, |arg| arg == "expand") {
        args.next().unwrap(); // expand
    }
    nightly.args(args);

    // Hopefully prevent infinite re-run loop.
    nightly.env(NO_RUN_NIGHTLY, "");

    let status = nightly.status()?;

    Ok(match status.code() {
        Some(code) => code,
        None => {
            if status.success() {
                0
            } else {
                1
            }
        }
    })
}

fn definitely_not_nightly() -> bool {
    let mut cmd = Command::new(cargo_binary());
    cmd.arg("--version");

    let output = match cmd.output() {
        Ok(output) => output,
        Err(_) => return false,
    };

    let version = match String::from_utf8(output.stdout) {
        Ok(version) => version,
        Err(_) => return false,
    };

    version.starts_with("cargo 1") && !version.contains("nightly")
}

fn cargo_binary() -> OsString {
    env::var_os("CARGO").unwrap_or_else(|| "cargo".to_owned().into())
}

fn cargo_expand() -> Result<i32> {
    let Opts::Expand(args) = Opts::from_args();

    let mut builder = tempfile::Builder::new();
    builder.prefix("cargo-expand");
    let outdir = builder.tempdir().expect("failed to create tmp file");
    let outfile_path = outdir.path().join("expanded");

    // Run cargo
    let mut cmd = Command::new(cargo_binary());
    apply_args(&mut cmd, &args, &outfile_path);
    let code = filter_err(&mut cmd, ignore_cargo_err)?;

    let mut outfile = File::open(&outfile_path)?;
    if outfile.metadata()?.len() == 0 {
        let _ = writeln!(
            &mut io::stderr(),
            "ERROR: rustc produced no expanded output"
        );
        return Ok(if code == 0 { 1 } else { code });
    }

    // Run rustfmt
    let which_rustfmt = which(&["rustfmt"]);
    if let Some(fmt) = which_rustfmt {
        // Discard comments, which are misplaced by the compiler
        let mut content = Vec::new();
        outfile.read_to_end(&mut content)?;
        match String::from_utf8(content) {
            Ok(content) => {
                if let Ok(syntax_tree) = syn::parse_file(&content) {
                    let content = quote!(#syntax_tree).to_string();
                    fs::write(&outfile_path, content)?;
                }
            }
            Err(_) => {
                let _ = writeln!(&mut io::stderr(), "WARNING: non-UTF8 content");
            }
        }

        // Ignore any errors.
        let _status = Command::new(fmt)
            .arg(&outfile_path)
            .stderr(Stdio::null())
            .status();
    }

    // Run pretty printer
    let do_color = match args.color.as_ref().map(String::as_str) {
        Some("always") => true,
        Some("never") => false,
        None | Some("auto") | Some(_) => atty::is(Stdout),
    };
    if do_color {
        let mut content = fs::read_to_string(&outfile_path)?;
        if content.ends_with('\n') {
            // Pretty printer seems to print an extra trailing newline.
            content.truncate(content.len() - 1);
        }
        let printer = PrettyPrinter::default()
            .header(false)
            .grid(false)
            .line_numbers(false)
            .language("rust")
            .paging_mode(PagingMode::Never)
            .build()
            .unwrap();

        // Ignore any errors.
        let _ = printer.string(content);
    } else {
        let mut reader = File::open(&outfile_path)?;
        io::copy(&mut reader, &mut io::stdout())?;
    }

    Ok(0)
}

// Based on https://github.com/rsolomo/cargo-check
fn apply_args(cmd: &mut Command, args: &Args, outfile: &Path) {
    cmd.arg("rustc");
    cmd.arg("--profile=check");

    if let Some(features) = &args.features {
        cmd.arg("--features");
        cmd.arg(features);
    }

    if args.all_features {
        cmd.arg("--all-features");
    }

    if args.no_default_features {
        cmd.arg("--no-default-features");
    }

    if args.lib {
        cmd.arg("--lib");
    }

    if let Some(bin) = &args.bin {
        cmd.arg("--bin");
        cmd.arg(bin);
    }

    if let Some(example) = &args.example {
        cmd.arg("--example");
        cmd.arg(example);
    }

    if let Some(test) = &args.test {
        cmd.arg("--test");
        cmd.arg(test);
    }

    if let Some(bench) = &args.bench {
        cmd.arg("--bench");
        cmd.arg(bench);
    }

    if let Some(target) = &args.target {
        cmd.arg("--target");
        cmd.arg(target);
    }

    if let Some(target_dir) = &args.target_dir {
        cmd.arg("--target-dir");
        cmd.arg(target_dir);
    }

    if let Some(manifest_path) = &args.manifest_path {
        cmd.arg("--manifest-path");
        cmd.arg(manifest_path);
    }

    if let Some(jobs) = args.jobs {
        cmd.arg("--jobs");
        cmd.arg(jobs.to_string());
    }

    cmd.arg("--color");
    if let Some(color) = &args.color {
        cmd.arg(color);
    } else {
        cmd.arg(if atty::is(Stderr) { "always" } else { "never" });
    }

    if args.frozen {
        cmd.arg("--frozen");
    }

    if args.locked {
        cmd.arg("--locked");
    }

    for unstable_flag in &args.unstable_flags {
        cmd.arg("-Z");
        cmd.arg(unstable_flag);
    }

    cmd.arg("--");
    cmd.arg("-o");
    cmd.arg(outfile);
    cmd.arg("-Zunstable-options");
    cmd.arg("--pretty=expanded");
}

fn which(cmd: &[&str]) -> Option<OsString> {
    if env::args_os().any(|arg| arg == *"--help") {
        return None;
    }

    if let Some(which) = env::var_os(&cmd[0].to_uppercase()) {
        return if which.is_empty() { None } else { Some(which) };
    }

    let spawn = Command::new(cmd[0])
        .args(&cmd[1..])
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn();
    let mut child = match spawn {
        Ok(child) => child,
        Err(_) => {
            return None;
        }
    };

    let exit = match child.wait() {
        Ok(exit) => exit,
        Err(_) => {
            return None;
        }
    };

    if exit.success() {
        Some(cmd[0].into())
    } else {
        None
    }
}

fn filter_err(cmd: &mut Command, ignore: fn(&str) -> bool) -> io::Result<i32> {
    let mut child = cmd.stderr(Stdio::piped()).spawn()?;
    let mut stderr = io::BufReader::new(child.stderr.take().unwrap());
    let mut line = String::new();
    while let Ok(n) = stderr.read_line(&mut line) {
        if n == 0 {
            break;
        }
        if !ignore(&line) {
            let _ = write!(&mut io::stderr(), "{}", line);
        }
        line.clear();
    }
    let code = child.wait()?.code().unwrap_or(1);
    Ok(code)
}

fn ignore_cargo_err(line: &str) -> bool {
    if line.trim().is_empty() {
        return true;
    }

    let blacklist = [
        "ignoring specified output filename because multiple outputs were \
         requested",
        "ignoring specified output filename for 'link' output because multiple \
         outputs were requested",
        "ignoring --out-dir flag due to -o flag",
        "ignoring -C extra-filename flag due to -o flag",
        "due to multiple output types requested, the explicitly specified \
         output file name will be adapted for each output type",
    ];
    for s in &blacklist {
        if line.contains(s) {
            return true;
        }
    }

    false
}
<|endoftext|>


<commit_before>extern crate chan_signal;
#[macro_use]
extern crate clap;
extern crate env_logger;
extern crate fuse;
extern crate libc;
#[macro_use]
extern crate log;
extern crate time;

use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::io;
use std::path::Path;
use std::str::FromStr;
use std::thread;

use chan_signal::Signal;
use clap::{App, Arg};
use env_logger::LogBuilder;
use log::LogRecord;

mod catfs;
mod flags;
mod evicter;

use catfs::error;
use catfs::flags::{DiskSpace, FlagStorage};
use catfs::rlibc;

fn main() {
    if let Err(e) = main_internal() {
        error!("Cannot mount: {}", e);
        std::process::exit(1);
    }
}

fn main_internal() -> error::Result<()> {
    let format = |record: &LogRecord| {
        let t = time::now();
        format!(
            "{} {:5} - {}",
            time::strftime("%Y-%m-%d %H:%M:%S", &t).unwrap(),
            record.level(),
            record.args()
        )
    };

    let mut builder = LogBuilder::new();
    builder.format(format);

    if env::var("RUST_LOG").is_ok() {
        builder.parse(&env::var("RUST_LOG").unwrap());
    }

    builder.init().unwrap();

    let mut flags: FlagStorage = Default::default();
    let mut test = false;

    let app = App::new("catfs")
        .about("Cache Anything FileSystem")
        .version(crate_version!());

    {
        fn diskspace_validator(s: String) -> Result<(), String> {
            DiskSpace::from_str(&s).map(|_| ()).map_err(
                |e| e.to_str().to_owned(),
            )
        }

        fn path_validator(s: String) -> Result<(), String> {
            Path::new(&s)
                .canonicalize()
                .map_err(|e| e.description().to_owned())
                .and_then(|p| if p.is_dir() {
                    Ok(())
                } else {
                    Err("is not a directory".to_owned())
                })
        }

        let mut args = [
            flags::Flag {
                arg: Arg::with_name("space")
                    .long("free")
                    .takes_value(true)
                    .help(
                        "Ensure filesystem has at least this much free space. (ex: 9.5%, 10G)",
                    )
                    .validator(diskspace_validator),
                value: &mut flags.free_space,
            },
            flags::Flag {
                arg: Arg::with_name("foreground").short("f").help(
                    "Run catfs in foreground.",
                ),
                value: &mut flags.foreground,
            },
            flags::Flag {
                arg: Arg::with_name("option").short("o").takes_value(true).help(
                    "Additional system-specific mount options. Be careful!",
                ),
                value: &mut flags.mount_options,
            },
            flags::Flag {
                arg: Arg::with_name("test").long("test").help(
                    "Exit after parsing arguments",
                ),
                value: &mut test,
            },
            flags::Flag {
                arg: Arg::with_name("from")
                    .index(1)
                    .required(true)
                    .help("Cache files from this directory.")
                    .validator(path_validator),
                value: &mut flags.cat_from,
            },
            flags::Flag {
                arg: Arg::with_name("to")
                    .index(2)
                    .required(true)
                    .help("Cache files to this directory.")
                    .validator(path_validator),
                value: &mut flags.cat_to,
            },
            flags::Flag {
                arg: Arg::with_name("mountpoint")
                    .index(3)
                    .required(true)
                    .help("Expose the mount point at this directory.")
                    .validator(path_validator),
                value: &mut flags.mount_point,
            },
        ];

        flags::parse_options(app, &mut args);
    }

    if test {
        return Ok(());
    }

    let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    let path_from = Path::new(&flags.cat_from).canonicalize()?;
    let path_to = Path::new(&flags.cat_to).canonicalize()?;
    let fs = catfs::CatFS::new(&path_from, &path_to)?;
    let cache_dir = fs.get_cache_dir()?;
    let mut options: Vec<&OsStr> = Vec::new();
    for i in 0..flags.mount_options.len() {
        options.push(&flags.mount_options[i]);
    }

    {
        let mut session = fuse::Session::new(fs, Path::new(&flags.mount_point), &options)?;
        thread::spawn(move || {
            if let Err(e) = session.run() {
                error!("session.run() = {}", e);
            }
            info!("{:?} unmounted", session.mountpoint());
            unsafe { libc::kill(libc::getpid(), libc::SIGTERM) };
        });

        let mut ev = evicter::Evicter::new(cache_dir, &flags.free_space);
        ev.run();
        // unmount after we get signaled becausep session will go out of scope
        let s = signal.recv().unwrap();
        info!(
            "Received {:?}, attempting to unmount {:?}",
            s,
            flags.mount_point
        );
        unmount(Path::new(&flags.mount_point))?;
    }
    rlibc::close(cache_dir)?;
    return Ok(());
}

use libc::{c_char, c_int};
use std::ffi::{CString, CStr};
/// Unmount an arbitrary mount point
pub fn unmount(mountpoint: &Path) -> io::Result<()> {
    // fuse_unmount_compat22 unfortunately doesn't return a status. Additionally,
    // it attempts to call realpath, which in turn calls into the filesystem. So
    // if the filesystem returns an error, the unmount does not take place, with
    // no indication of the error available to the caller. So we call unmount
    // directly, which is what osxfuse does anyway, since we already converted
    // to the real path when we first mounted.

    #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly",
                target_os = "openbsd", target_os = "bitrig", target_os = "netbsd"))]
    #[inline]
    fn libc_umount(mnt: &CStr) -> c_int {
        unsafe { libc::unmount(mnt.as_ptr(), 0) }
    }

    #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly",
                      target_os = "openbsd", target_os = "bitrig", target_os = "netbsd")))]
    #[inline]
    fn libc_umount(mnt: &CStr) -> c_int {
        use std::io::ErrorKind::PermissionDenied;

        let rc = unsafe { libc::umount(mnt.as_ptr()) };
        if rc < 0 && io::Error::last_os_error().kind() == PermissionDenied {
            // Linux always returns EPERM for non-root users.  We have to let the
            // library go through the setuid-root "fusermount -u" to unmount.
            unsafe {
                fuse_unmount_compat22(mnt.as_ptr());
            }
            0
        } else {
            rc
        }
    }

    let mnt = try!(CString::new(mountpoint.as_os_str().as_bytes()));
    let rc = libc_umount(&mnt);
    if rc < 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(())
    }
}

extern "system" {
    pub fn fuse_unmount_compat22(mountpoint: *const c_char);
}
<commit_msg>always set default_permissions<commit_after>extern crate chan_signal;
#[macro_use]
extern crate clap;
extern crate env_logger;
extern crate fuse;
extern crate libc;
#[macro_use]
extern crate log;
extern crate time;

use std::env;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::os::unix::ffi::OsStrExt;
use std::io;
use std::path::Path;
use std::str::FromStr;
use std::thread;

use chan_signal::Signal;
use clap::{App, Arg};
use env_logger::LogBuilder;
use log::LogRecord;

mod catfs;
mod flags;
mod evicter;

use catfs::error;
use catfs::flags::{DiskSpace, FlagStorage};
use catfs::rlibc;

fn main() {
    if let Err(e) = main_internal() {
        error!("Cannot mount: {}", e);
        std::process::exit(1);
    }
}

fn main_internal() -> error::Result<()> {
    let format = |record: &LogRecord| {
        let t = time::now();
        format!(
            "{} {:5} - {}",
            time::strftime("%Y-%m-%d %H:%M:%S", &t).unwrap(),
            record.level(),
            record.args()
        )
    };

    let mut builder = LogBuilder::new();
    builder.format(format);

    if env::var("RUST_LOG").is_ok() {
        builder.parse(&env::var("RUST_LOG").unwrap());
    }

    builder.init().unwrap();

    let mut flags: FlagStorage = Default::default();
    let mut test = false;

    let app = App::new("catfs")
        .about("Cache Anything FileSystem")
        .version(crate_version!());

    {
        fn diskspace_validator(s: String) -> Result<(), String> {
            DiskSpace::from_str(&s).map(|_| ()).map_err(
                |e| e.to_str().to_owned(),
            )
        }

        fn path_validator(s: String) -> Result<(), String> {
            Path::new(&s)
                .canonicalize()
                .map_err(|e| e.description().to_owned())
                .and_then(|p| if p.is_dir() {
                    Ok(())
                } else {
                    Err("is not a directory".to_owned())
                })
        }

        let mut args = [
            flags::Flag {
                arg: Arg::with_name("space")
                    .long("free")
                    .takes_value(true)
                    .help(
                        "Ensure filesystem has at least this much free space. (ex: 9.5%, 10G)",
                    )
                    .validator(diskspace_validator),
                value: &mut flags.free_space,
            },
            flags::Flag {
                arg: Arg::with_name("foreground").short("f").help(
                    "Run catfs in foreground.",
                ),
                value: &mut flags.foreground,
            },
            flags::Flag {
                arg: Arg::with_name("option").short("o").takes_value(true).help(
                    "Additional system-specific mount options. Be careful!",
                ),
                value: &mut flags.mount_options,
            },
            flags::Flag {
                arg: Arg::with_name("test").long("test").help(
                    "Exit after parsing arguments",
                ),
                value: &mut test,
            },
            flags::Flag {
                arg: Arg::with_name("from")
                    .index(1)
                    .required(true)
                    .help("Cache files from this directory.")
                    .validator(path_validator),
                value: &mut flags.cat_from,
            },
            flags::Flag {
                arg: Arg::with_name("to")
                    .index(2)
                    .required(true)
                    .help("Cache files to this directory.")
                    .validator(path_validator),
                value: &mut flags.cat_to,
            },
            flags::Flag {
                arg: Arg::with_name("mountpoint")
                    .index(3)
                    .required(true)
                    .help("Expose the mount point at this directory.")
                    .validator(path_validator),
                value: &mut flags.mount_point,
            },
        ];

        flags::parse_options(app, &mut args);
    }

    if test {
        return Ok(());
    }

    flags.mount_options.push(
        OsString::from("default_permissions"),
    );

    let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    let path_from = Path::new(&flags.cat_from).canonicalize()?;
    let path_to = Path::new(&flags.cat_to).canonicalize()?;
    let fs = catfs::CatFS::new(&path_from, &path_to)?;
    let cache_dir = fs.get_cache_dir()?;
    let mut options: Vec<&OsStr> = Vec::new();
    for i in 0..flags.mount_options.len() {
        options.push(&flags.mount_options[i]);
    }

    {
        let mut session = fuse::Session::new(fs, Path::new(&flags.mount_point), &options)?;
        thread::spawn(move || {
            if let Err(e) = session.run() {
                error!("session.run() = {}", e);
            }
            info!("{:?} unmounted", session.mountpoint());
            unsafe { libc::kill(libc::getpid(), libc::SIGTERM) };
        });

        let mut ev = evicter::Evicter::new(cache_dir, &flags.free_space);
        ev.run();
        // unmount after we get signaled becausep session will go out of scope
        let s = signal.recv().unwrap();
        info!(
            "Received {:?}, attempting to unmount {:?}",
            s,
            flags.mount_point
        );
        unmount(Path::new(&flags.mount_point))?;
    }
    rlibc::close(cache_dir)?;
    return Ok(());
}

use libc::{c_char, c_int};
use std::ffi::{CString, CStr};
/// Unmount an arbitrary mount point
pub fn unmount(mountpoint: &Path) -> io::Result<()> {
    // fuse_unmount_compat22 unfortunately doesn't return a status. Additionally,
    // it attempts to call realpath, which in turn calls into the filesystem. So
    // if the filesystem returns an error, the unmount does not take place, with
    // no indication of the error available to the caller. So we call unmount
    // directly, which is what osxfuse does anyway, since we already converted
    // to the real path when we first mounted.

    #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly",
                target_os = "openbsd", target_os = "bitrig", target_os = "netbsd"))]
    #[inline]
    fn libc_umount(mnt: &CStr) -> c_int {
        unsafe { libc::unmount(mnt.as_ptr(), 0) }
    }

    #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly",
                      target_os = "openbsd", target_os = "bitrig", target_os = "netbsd")))]
    #[inline]
    fn libc_umount(mnt: &CStr) -> c_int {
        use std::io::ErrorKind::PermissionDenied;

        let rc = unsafe { libc::umount(mnt.as_ptr()) };
        if rc < 0 && io::Error::last_os_error().kind() == PermissionDenied {
            // Linux always returns EPERM for non-root users.  We have to let the
            // library go through the setuid-root "fusermount -u" to unmount.
            unsafe {
                fuse_unmount_compat22(mnt.as_ptr());
            }
            0
        } else {
            rc
        }
    }

    let mnt = try!(CString::new(mountpoint.as_os_str().as_bytes()));
    let rc = libc_umount(&mnt);
    if rc < 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(())
    }
}

extern "system" {
    pub fn fuse_unmount_compat22(mountpoint: *const c_char);
}
<|endoftext|>


<commit_before>#![feature(collections, core, env, libc, old_io, old_path, plugin, std_misc)]

// Other platforms than macos don't need std_misc but you can't
// use #[cfg] on features.
#![allow(unused_features)]

extern crate ansi_term;
extern crate datetime;
extern crate getopts;
extern crate locale;
extern crate natord;
extern crate number_prefix;
extern crate pad;
extern crate users;

#[cfg(feature="git")]
extern crate git2;

use std::env;
use std::old_io::{fs, FileType};

use dir::Dir;
use file::File;
use options::{Options, View};
use output::lines_view;

pub mod column;
pub mod dir;
pub mod file;
pub mod filetype;
pub mod options;
pub mod output;
pub mod term;
pub mod xattr;

struct Exa<'a> {
    count:   usize,
    options: Options,
    dirs:    Vec<Path>,
    files:   Vec<File<'a>>,
}

impl<'a> Exa<'a> {
    fn new(options: Options) -> Exa<'a> {
        Exa {
            count: 0,
            options: options,
            dirs: Vec::new(),
            files: Vec::new(),
        }
    }

    fn load<T>(&mut self, iter: T) where T: Iterator<Item = &'a String> {
        // Separate the user-supplied paths into directories and files.
        // Files are shown first, and then each directory is expanded
        // and listed second.
        for file in iter {
            let path = Path::new(file);
            match fs::stat(&path) {
                Ok(stat) => {
                    if stat.kind == FileType::Directory {
                        if self.options.dir_action.is_tree() {
                            self.files.push(File::with_stat(stat, &path, None, true));
                        }
                        else {
                            self.dirs.push(path);
                        }
                    }
                    else {
                        self.files.push(File::with_stat(stat, &path, None, false));
                    }
                }
                Err(e) => println!("{}: {}", file, e),
            }

            self.count += 1;
        }
    }

    fn print_files(&self) {
        if !self.files.is_empty() {
            self.print(None, &self.files[..]);
        }
    }

    fn print_dirs(&mut self) {
        let mut first = self.files.is_empty();

        // Directories are put on a stack rather than just being iterated through,
        // as the vector can change as more directories are added.
        loop {
            let dir_path = match self.dirs.pop() {
                None => break,
                Some(f) => f,
            };

            // Put a gap between directories, or between the list of files and the
            // first directory.
            if first {
                first = false;
            }
            else {
                print!("\n");
            }

            match Dir::readdir(&dir_path) {
                Ok(ref dir) => {
                    let mut files = dir.files(false);
                    self.options.transform_files(&mut files);

                    // When recursing, add any directories to the dirs stack
                    // backwards: the *last* element of the stack is used each
                    // time, so by inserting them backwards, they get displayed in
                    // the correct sort order.
                    if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                        let depth = dir_path.components().filter(|&c| c != b".").count() + 1;
                        if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
                            for dir in files.iter().filter(|f| f.stat.kind == FileType::Directory).rev() {
                                self.dirs.push(dir.path.clone());
                            }
                        }
                    }

                    if self.count > 1 {
                        println!("{}:", dir_path.display());
                    }
                    self.count += 1;

                    self.print(Some(dir), &files[..]);
                }
                Err(e) => {
                    println!("{}: {}", dir_path.display(), e);
                    return;
                }
            };
        }
    }

    fn print(&self, dir: Option<&Dir>, files: &[File]) {
        match self.options.view {
            View::Grid(g)     => g.view(files),
            View::Details(d)  => d.view(dir, files),
            View::Lines       => lines_view(files),
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();

    match Options::getopts(args.tail()) {
        Ok((options, paths)) => {
            let mut exa = Exa::new(options);
            exa.load(paths.iter());
            exa.print_files();
            exa.print_dirs();
        },
        Err(e) => {
            println!("{}", e);
            env::set_exit_status(e.error_code());
        },
    };
}
<commit_msg>We don't use plugin anymore<commit_after>#![feature(collections, core, env, libc, old_io, old_path, std_misc)]

// Other platforms than macos don't need std_misc but you can't
// use #[cfg] on features.
#![allow(unused_features)]

extern crate ansi_term;
extern crate datetime;
extern crate getopts;
extern crate locale;
extern crate natord;
extern crate number_prefix;
extern crate pad;
extern crate users;

#[cfg(feature="git")]
extern crate git2;

use std::env;
use std::old_io::{fs, FileType};

use dir::Dir;
use file::File;
use options::{Options, View};
use output::lines_view;

pub mod column;
pub mod dir;
pub mod file;
pub mod filetype;
pub mod options;
pub mod output;
pub mod term;
pub mod xattr;

struct Exa<'a> {
    count:   usize,
    options: Options,
    dirs:    Vec<Path>,
    files:   Vec<File<'a>>,
}

impl<'a> Exa<'a> {
    fn new(options: Options) -> Exa<'a> {
        Exa {
            count: 0,
            options: options,
            dirs: Vec::new(),
            files: Vec::new(),
        }
    }

    fn load<T>(&mut self, iter: T) where T: Iterator<Item = &'a String> {
        // Separate the user-supplied paths into directories and files.
        // Files are shown first, and then each directory is expanded
        // and listed second.
        for file in iter {
            let path = Path::new(file);
            match fs::stat(&path) {
                Ok(stat) => {
                    if stat.kind == FileType::Directory {
                        if self.options.dir_action.is_tree() {
                            self.files.push(File::with_stat(stat, &path, None, true));
                        }
                        else {
                            self.dirs.push(path);
                        }
                    }
                    else {
                        self.files.push(File::with_stat(stat, &path, None, false));
                    }
                }
                Err(e) => println!("{}: {}", file, e),
            }

            self.count += 1;
        }
    }

    fn print_files(&self) {
        if !self.files.is_empty() {
            self.print(None, &self.files[..]);
        }
    }

    fn print_dirs(&mut self) {
        let mut first = self.files.is_empty();

        // Directories are put on a stack rather than just being iterated through,
        // as the vector can change as more directories are added.
        loop {
            let dir_path = match self.dirs.pop() {
                None => break,
                Some(f) => f,
            };

            // Put a gap between directories, or between the list of files and the
            // first directory.
            if first {
                first = false;
            }
            else {
                print!("\n");
            }

            match Dir::readdir(&dir_path) {
                Ok(ref dir) => {
                    let mut files = dir.files(false);
                    self.options.transform_files(&mut files);

                    // When recursing, add any directories to the dirs stack
                    // backwards: the *last* element of the stack is used each
                    // time, so by inserting them backwards, they get displayed in
                    // the correct sort order.
                    if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                        let depth = dir_path.components().filter(|&c| c != b".").count() + 1;
                        if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
                            for dir in files.iter().filter(|f| f.stat.kind == FileType::Directory).rev() {
                                self.dirs.push(dir.path.clone());
                            }
                        }
                    }

                    if self.count > 1 {
                        println!("{}:", dir_path.display());
                    }
                    self.count += 1;

                    self.print(Some(dir), &files[..]);
                }
                Err(e) => {
                    println!("{}: {}", dir_path.display(), e);
                    return;
                }
            };
        }
    }

    fn print(&self, dir: Option<&Dir>, files: &[File]) {
        match self.options.view {
            View::Grid(g)     => g.view(files),
            View::Details(d)  => d.view(dir, files),
            View::Lines       => lines_view(files),
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();

    match Options::getopts(args.tail()) {
        Ok((options, paths)) => {
            let mut exa = Exa::new(options);
            exa.load(paths.iter());
            exa.print_files();
            exa.print_dirs();
        },
        Err(e) => {
            println!("{}", e);
            env::set_exit_status(e.error_code());
        },
    };
}
<|endoftext|>


<commit_before>#![feature(advanced_slice_patterns, slice_patterns)]

extern crate git_together;

use std::env;
use std::process::Command;
use git_together::GitTogether;
use git_together::config::GitConfig;
use git_together::errors::*;

fn main() {
  run(|| {
    let config = GitConfig { namespace: "git-together".into() };
    let gt = GitTogether { config: config };

    let all_args: Vec<_> = env::args().skip(1).collect();
    let args: Vec<&str> = all_args.iter().map(String::as_ref).collect();

    match &args[..] {
      &["with", ref inits..] => {
        try!(gt.set_active(inits));
      }
      &[sub_cmd, ref rest..] if sub_cmd == "commit" => {
        let mut git_cmd = Command::new("git");
        let cmd = git_cmd.arg(sub_cmd).args(rest);
        let signoff = try!(gt.signoff(cmd));

        try!(signoff.status().chain_err(|| ""));
        try!(gt.rotate_active());
      }
      args => {
        try!(Command::new("git").args(args).status().chain_err(|| ""));
      }
    };

    Ok(())
  })
}

fn run<F>(f: F)
  where F: Fn() -> Result<()>
{
  match f() {
    Err(e) => {
      println!("{}", e);
      std::process::exit(1);
    }
    _ => {}
  }
}
<commit_msg>use if let instead of match<commit_after>#![feature(advanced_slice_patterns, slice_patterns)]

extern crate git_together;

use std::env;
use std::process::Command;
use git_together::GitTogether;
use git_together::config::GitConfig;
use git_together::errors::*;

fn main() {
  run(|| {
    let config = GitConfig { namespace: "git-together".into() };
    let gt = GitTogether { config: config };

    let all_args: Vec<_> = env::args().skip(1).collect();
    let args: Vec<&str> = all_args.iter().map(String::as_ref).collect();

    match &args[..] {
      &["with", ref inits..] => {
        try!(gt.set_active(inits));
      }
      &[sub_cmd, ref rest..] if sub_cmd == "commit" => {
        let mut git_cmd = Command::new("git");
        let cmd = git_cmd.arg(sub_cmd).args(rest);
        let signoff = try!(gt.signoff(cmd));

        try!(signoff.status().chain_err(|| ""));
        try!(gt.rotate_active());
      }
      args => {
        try!(Command::new("git").args(args).status().chain_err(|| ""));
      }
    };

    Ok(())
  })
}

fn run<F>(f: F)
  where F: Fn() -> Result<()>
{
  if let Err(e) = f() {
    println!("{}", e);
    std::process::exit(1);
  }
}
<|endoftext|>


<commit_before>#![feature(path, io)]
use std::old_io as io;

struct State {
    index: usize,
    memory: [u8; 256]
}

type Trace<'a> = Fn(&mut State)+'a;

fn main() {
    let mut reader = reader(&Path::new("test.bf"));
    let mut state = State { index: 0, memory: [0; 256] };
    let mut chars = reader.chars().peekable();

    loop {
        let trace = parse(
            |&:| match chars.next() { Some(Ok(c)) => Some(c), _ => None },
        );
        match trace {
            Some(t) => (*t)(&mut state),
            None => return
        }
    }
}

fn reader(path: &Path) -> io::BufferedReader<io::File> {
    match io::File::open_mode(path, io::Open, io::Read) {
        Ok(f) => io::BufferedReader::new(f),
        Err(e) => panic!(e)
    }
}

fn parse<'a, F: FnMut() -> Option<char>>(mut next: F) -> Option<Box<Trace<'a>>> {
    match next() {
        Some('+') => Some(Box::new(|s| s.memory[s.index] += 1)),
        Some('-') => Some(Box::new(|s| s.memory[s.index] -= 1)),
        Some('.') => Some(Box::new(|s| print!("{}", s.memory[s.index]))),
        _ => None
    }
}
<commit_msg>feat: basic BF loop<commit_after>#![feature(core, path, io)]
use std::old_io as io;

type Ops = Vec<Op>;

#[derive(Debug)]
enum Op {
    Add(u8),
    Mov(usize),
    In,
    Out,
    Loop(Ops),
    Noop
}

struct State {
    index: usize,
    memory: [u8; 256]
}

impl State {
    fn peek(&self) -> u8 {
        self.memory[self.index % 256]
    }

    fn poke(&mut self, value: u8) {
        self.memory[self.index % 256] = value;
    }

    fn step(&mut self, op: &Op) {
        match op {
            &Op::Add(i) => { let x = self.peek(); self.poke(x + i); },
            &Op::Mov(n) => self.index += n,
            &Op::Out => print!("{}", std::str::from_utf8(vec![self.peek()].as_slice()).unwrap()),
            &Op::Loop(ref ops) => {
                while self.peek() != 0 {
                    for op in ops.as_slice() {
                        self.step(op);
                    }
                }
            },
            _ => {}
        }
    }

    fn run(&mut self, ops: Ops) {
        for op in ops {
        }
    }
}

fn main() {
    let mut reader = reader(&Path::new("hello.bf"));
    let mut state = State { index: 0, memory: [0; 256] };
    let mut chars = reader.chars().peekable();

    loop {
        match parse(&mut chars) {
            Some(op) => state.step(&op),
            None => return
        }
    }
}

fn parse(mut chars: &mut std::iter::Peekable<io::Chars<io::BufferedReader<io::fs::File>>>) -> Option<Op> {
    match chars.next() {
        Some(Ok('+')) => Some(Op::Add(1)),
        Some(Ok('-')) => Some(Op::Add(-1)),
        Some(Ok('<')) => Some(Op::Mov(-1)),
        Some(Ok('>')) => Some(Op::Mov(1)),
        Some(Ok('.')) => Some(Op::Out),
        Some(Ok('[')) => {
            let mut children: Ops = Vec::new();
            while !(chars.peek() == Some(&Ok(']'))) {
                match parse(&mut chars) {
                    Some(op) => children.push(op),
                    None => {}
                }
            }
            chars.next();
            Some(Op::Loop(children))
        },
        Some(_) => Some(Op::Noop),
        _ => None
    }
}

fn reader(path: &Path) -> io::BufferedReader<io::File> {
    match io::File::open_mode(path, io::Open, io::Read) {
        Ok(f) => io::BufferedReader::new(f),
        Err(e) => panic!(e)
    }
}
<|endoftext|>


<commit_before>extern crate alpm;
#[macro_use]
extern crate clap;
extern crate curl;
extern crate env_logger;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate serde_json;

use clap::App;
use curl::easy::Easy;
use itertools::Itertools;
use serde_json::Value;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry::{Occupied, Vacant};
use std::default::Default;
use std::process::exit;
use std::str;

mod avg;
mod enums;

struct Options {
    format: Option<String>,
    quiet: u64,
    upgradable_only: bool,
}

impl Default for Options {
    fn default() -> Options {
        Options {
            format: None,
            quiet: 0,
            upgradable_only: false,
        }
    }
}

fn main() {
    env_logger::init().expect("env_logger failed");

    let yaml = load_yaml!("cli.yml");
    let args = App::from_yaml(yaml).get_matches();

    let options = Options {
        format: {
            match args.value_of("format") {
                Some(f) => Some(f.to_string()),
                None => None,
            }
        },
        quiet: args.occurrences_of("quiet"),
        upgradable_only: args.is_present("upgradable"),
    };

    let mut avgs = String::new();
    {
        info!("Downloading AVGs...");
        let avgs_url = "https://security.archlinux.org/issues/all.json";

        let mut easy = Easy::new();
        easy.url(avgs_url).expect("curl::Easy::url failed");
        let mut transfer = easy.transfer();
        transfer
            .write_function(|data| {
                                avgs.push_str(str::from_utf8(data).expect("str conversion failed"));
                                Ok(data.len())
                            })
            .expect("write_function failed");
        match transfer.perform() {
            Ok(_) => {}
            Err(_) => {
                println!("Cannot fetch data, please check your network connection!");
                exit(1)
            }
        };
    }

    let pacman = match args.value_of("dbpath") {
        Some(path) => {
            alpm::Alpm::with_dbpath(path.to_string()).expect("alpm::Alpm::with_dbpath failed")
        }
        None => alpm::Alpm::new().expect("alpm::Alpm::new failed"),
    };

    let mut cves: BTreeMap<String, Vec<_>> = BTreeMap::new();
    {
        let json: Value = serde_json::from_str(&avgs).expect("serde_json::from_str failed");

        for avg in json.as_array().expect("Value::as_array failed") {
            let packages = avg["packages"]
                .as_array()
                .expect("Value::as_array failed")
                .iter()
                .map(|s| s.as_str().expect("Value::as_str failed").to_string())
                .collect::<Vec<_>>();

            if !package_is_installed(&pacman, &packages) {
                continue;
            }

            let info = to_avg(avg);

            if info.status != enums::Status::NotAffected {
                for p in packages {
                    match cves.entry(p) {
                            Occupied(c) => c.into_mut(),
                            Vacant(c) => c.insert(vec![]),
                        }
                        .push(info.clone());
                }
            }
        }
    }

    let mut affected_avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();
    for (pkg, avgs) in cves {
        for avg in &avgs {
            if system_is_affected(&pacman, &pkg, avg) {
                match affected_avgs.entry(pkg.clone()) {
                        Occupied(c) => c.into_mut(),
                        Vacant(c) => c.insert(vec![]),
                    }
                    .push(avg.clone());
            }
        }
    }

    let merged = merge_avgs(&pacman, &affected_avgs);
    print_avgs(&options, &merged);
}

/// Converts a JSON to an `avg::AVG`
fn to_avg(data: &Value) -> avg::AVG {
    avg::AVG {
        issues: data["issues"]
            .as_array()
            .expect("Value::as_array failed")
            .iter()
            .map(|s| s.as_str().expect("Value::as_str failed").to_string())
            .collect(),
        fixed: match data["fixed"].as_str() {
            Some(s) => Some(s.to_string()),
            None => None,
        },
        severity: data["severity"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Severity>()
            .expect("parse::<Severity> failed"),
        status: data["status"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Status>()
            .expect("parse::<Status> failed"),
    }
}

#[test]
fn test_to_avg() {
    let json: Value = serde_json::from_str("{\"issues\": [\"CVE-1\", \"CVE-2\"], \"fixed\": \"1.0\", \
                               \"severity\": \"High\", \"status\": \"Not affected\"}")
            .expect("serde_json::from_str failed");

    let avg1 = to_avg(&json);
    assert_eq!(2, avg1.issues.len());
    assert_eq!(Some("1.0".to_string()), avg1.fixed);
    assert_eq!(enums::Severity::High, avg1.severity);
    assert_eq!(enums::Status::NotAffected, avg1.status);

    let json: Value = serde_json::from_str("{\"issues\": [\"CVE-1\"], \"fixed\": null, \
                               \"severity\": \"Low\", \"status\": \"Vulnerable\"}")
            .expect("serde_json::from_str failed");

    let avg2 = to_avg(&json);
    assert_eq!(1, avg2.issues.len());
    assert_eq!(None, avg2.fixed);
    assert_eq!(enums::Severity::Low, avg2.severity);
    assert_eq!(enums::Status::Vulnerable, avg2.status);
}

/// Given a package and an `avg::AVG`, returns true if the system is affected
fn system_is_affected(pacman: &alpm::Alpm, pkg: &str, avg: &avg::AVG) -> bool {
    match pacman.query_package_version(pkg.clone()) {
        Ok(v) => {
            info!("Found installed version {} for package {}", v, pkg);
            match avg.fixed {
                Some(ref version) => {
                    info!("Comparing with fixed version {}", version);
                    let cmp = pacman
                        .vercmp(v.clone(), version.clone())
                        .expect("Alpm::vercmp failed");
                    if let Ordering::Less = cmp {
                        return true;
                    }
                }
                None => return true,
            };
        }
        Err(_) => debug!("Package {} not installed", pkg),
    }

    false
}

#[test]
fn test_system_is_affected() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert_eq!(false,
               system_is_affected(&pacman, &"pacman".to_string(), &avg1));

    let avg2 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("7.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert!(system_is_affected(&pacman, &"pacman".to_string(), &avg2));
}

/// Given a list of package names, returns true when at least one is installed
fn package_is_installed(pacman: &alpm::Alpm, packages: &[String]) -> bool {
    for pkg in packages {
        match pacman.query_package_version(pkg.as_str()) {
            Ok(_) => {
                info!("Package {} is installed", pkg);
                return true;
            }
            Err(_) => debug!("Package {} not installed", pkg),
        }
    }
    false
}

#[test]
fn test_package_is_installed() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let packages = vec!["pacman".to_string(), "pac".to_string()];
    assert!(package_is_installed(&pacman, &packages));

    let packages = vec!["pac".to_string()];
    assert_eq!(false, package_is_installed(&pacman, &packages));
}

/// Merge a list of `avg::AVG` into a single `avg::AVG` using major version as version
fn merge_avgs(pacman: &alpm::Alpm,
              cves: &BTreeMap<String, Vec<avg::AVG>>)
              -> BTreeMap<String, avg::AVG> {
    let mut avgs: BTreeMap<String, avg::AVG> = BTreeMap::new();
    for (pkg, list) in cves.iter() {
        let mut avg_issues = vec![];
        let mut avg_fixed: Option<String> = None;
        let mut avg_severity = enums::Severity::Unknown;
        let mut avg_status = enums::Status::Unknown;

        for a in list.iter() {
            avg_issues.append(&mut a.issues.clone());

            match avg_fixed.clone() {
                Some(ref version) => {
                    if let Some(ref v) = a.fixed {
                        let cmp = pacman
                            .vercmp(version.to_string(), v.to_string())
                            .expect("Alpm::vercmp failed");
                        if let Ordering::Greater = cmp {
                            avg_fixed = a.fixed.clone();
                        }
                    }
                }
                None => avg_fixed = a.fixed.clone(),
            }

            if a.severity > avg_severity {
                avg_severity = a.severity;
            }

            // We only care about testing stuff
            if a.status == enums::Status::Testing {
                avg_status = a.status.clone();
            }
        }

        let avg = avg::AVG {
            issues: avg_issues,
            fixed: avg_fixed,
            severity: avg_severity,
            status: avg_status,
        };
        avgs.insert(pkg.to_string(), avg);
    }

    avgs
}

#[test]
fn test_merge_avgs() {
    let mut avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Fixed,
    };

    let avg2 = avg::AVG {
        issues: vec!["CVE-4".to_string(), "CVE-10".to_string()],
        fixed: Some("0.9.8".to_string()),
        severity: enums::Severity::High,
        status: enums::Status::Testing,
    };

    assert!(enums::Severity::Critical > enums::Severity::High);

    avgs.insert("package".to_string(), vec![avg1.clone(), avg2.clone()]);

    avgs.insert("package2".to_string(), vec![avg1, avg2]);

    let pacman = alpm::Alpm::new().expect("Alpm::new failed");
    let merged = merge_avgs(&pacman, &avgs);

    assert_eq!(2, merged.len());
    assert_eq!(4,
               merged
                   .get(&"package".to_string())
                   .expect("'package' key not found")
                   .issues
                   .len());
    assert_eq!(enums::Severity::High,
               merged
                   .get(&"package".to_string())
                   .expect("'package' key not found")
                   .severity);
    assert_eq!(enums::Status::Testing,
               merged
                   .get(&"package".to_string())
                   .expect("'package' key not found")
                   .status);
}

/// Print a list of `avg::AVG`
fn print_avgs(options: &Options, avgs: &BTreeMap<String, avg::AVG>) {
    for (pkg, avg) in avgs {
        match avg.fixed {
            Some(ref v) => {
                if options.quiet >= 2 {
                    println!("{}", pkg);
                } else if options.quiet == 1 {
                    println!("{}>={}", pkg, v);
                } else {
                    match options.format {
                        Some(ref f) => {
                            println!("{}",
                                     f.replace("%n", pkg.as_str())
                                         .replace("%c", avg.issues.iter().join(",").as_str()))
                        }
                        None => {
                            print!("{}: Package {} is affected by {}.",
                                   avg.severity,
                                   pkg,
                                   avg.issues.join(", "));
                            if avg.status == enums::Status::Testing {
                                println!("Update to {} from testing repos!", v)
                            } else {
                                println!("Update to {}!", v)
                            }
                        }
                    }
                }
            }
            None => {
                if !options.upgradable_only {
                    if options.quiet > 0 {
                        println!("{}", pkg);
                    } else {
                        match options.format {
                            Some(ref f) => {
                                println!("{}",
                                         f.replace("%n", pkg.as_str())
                                             .replace("%c",
                                                      avg.issues.iter().join(",").as_str()))
                            }
                            None => {
                                println!("{}: Package {} is affected by {}.",
                                         avg.severity,
                                         pkg,
                                         avg.issues.join(", "));
                            }
                        }
                    }
                }
            }
        }
    }
}
<commit_msg>Restore output as it was before f5630f5<commit_after>extern crate alpm;
#[macro_use]
extern crate clap;
extern crate curl;
extern crate env_logger;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate serde_json;

use clap::App;
use curl::easy::Easy;
use itertools::Itertools;
use serde_json::Value;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry::{Occupied, Vacant};
use std::default::Default;
use std::process::exit;
use std::str;

mod avg;
mod enums;

struct Options {
    format: Option<String>,
    quiet: u64,
    upgradable_only: bool,
}

impl Default for Options {
    fn default() -> Options {
        Options {
            format: None,
            quiet: 0,
            upgradable_only: false,
        }
    }
}

fn main() {
    env_logger::init().expect("env_logger failed");

    let yaml = load_yaml!("cli.yml");
    let args = App::from_yaml(yaml).get_matches();

    let options = Options {
        format: {
            match args.value_of("format") {
                Some(f) => Some(f.to_string()),
                None => None,
            }
        },
        quiet: args.occurrences_of("quiet"),
        upgradable_only: args.is_present("upgradable"),
    };

    let mut avgs = String::new();
    {
        info!("Downloading AVGs...");
        let avgs_url = "https://security.archlinux.org/issues/all.json";

        let mut easy = Easy::new();
        easy.url(avgs_url).expect("curl::Easy::url failed");
        let mut transfer = easy.transfer();
        transfer
            .write_function(|data| {
                                avgs.push_str(str::from_utf8(data).expect("str conversion failed"));
                                Ok(data.len())
                            })
            .expect("write_function failed");
        match transfer.perform() {
            Ok(_) => {}
            Err(_) => {
                println!("Cannot fetch data, please check your network connection!");
                exit(1)
            }
        };
    }

    let pacman = match args.value_of("dbpath") {
        Some(path) => {
            alpm::Alpm::with_dbpath(path.to_string()).expect("alpm::Alpm::with_dbpath failed")
        }
        None => alpm::Alpm::new().expect("alpm::Alpm::new failed"),
    };

    let mut cves: BTreeMap<String, Vec<_>> = BTreeMap::new();
    {
        let json: Value = serde_json::from_str(&avgs).expect("serde_json::from_str failed");

        for avg in json.as_array().expect("Value::as_array failed") {
            let packages = avg["packages"]
                .as_array()
                .expect("Value::as_array failed")
                .iter()
                .map(|s| s.as_str().expect("Value::as_str failed").to_string())
                .collect::<Vec<_>>();

            if !package_is_installed(&pacman, &packages) {
                continue;
            }

            let info = to_avg(avg);

            if info.status != enums::Status::NotAffected {
                for p in packages {
                    match cves.entry(p) {
                            Occupied(c) => c.into_mut(),
                            Vacant(c) => c.insert(vec![]),
                        }
                        .push(info.clone());
                }
            }
        }
    }

    let mut affected_avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();
    for (pkg, avgs) in cves {
        for avg in &avgs {
            if system_is_affected(&pacman, &pkg, avg) {
                match affected_avgs.entry(pkg.clone()) {
                        Occupied(c) => c.into_mut(),
                        Vacant(c) => c.insert(vec![]),
                    }
                    .push(avg.clone());
            }
        }
    }

    let merged = merge_avgs(&pacman, &affected_avgs);
    print_avgs(&options, &merged);
}

/// Converts a JSON to an `avg::AVG`
fn to_avg(data: &Value) -> avg::AVG {
    avg::AVG {
        issues: data["issues"]
            .as_array()
            .expect("Value::as_array failed")
            .iter()
            .map(|s| s.as_str().expect("Value::as_str failed").to_string())
            .collect(),
        fixed: match data["fixed"].as_str() {
            Some(s) => Some(s.to_string()),
            None => None,
        },
        severity: data["severity"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Severity>()
            .expect("parse::<Severity> failed"),
        status: data["status"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Status>()
            .expect("parse::<Status> failed"),
    }
}

#[test]
fn test_to_avg() {
    let json: Value = serde_json::from_str("{\"issues\": [\"CVE-1\", \"CVE-2\"], \"fixed\": \"1.0\", \
                               \"severity\": \"High\", \"status\": \"Not affected\"}")
            .expect("serde_json::from_str failed");

    let avg1 = to_avg(&json);
    assert_eq!(2, avg1.issues.len());
    assert_eq!(Some("1.0".to_string()), avg1.fixed);
    assert_eq!(enums::Severity::High, avg1.severity);
    assert_eq!(enums::Status::NotAffected, avg1.status);

    let json: Value = serde_json::from_str("{\"issues\": [\"CVE-1\"], \"fixed\": null, \
                               \"severity\": \"Low\", \"status\": \"Vulnerable\"}")
            .expect("serde_json::from_str failed");

    let avg2 = to_avg(&json);
    assert_eq!(1, avg2.issues.len());
    assert_eq!(None, avg2.fixed);
    assert_eq!(enums::Severity::Low, avg2.severity);
    assert_eq!(enums::Status::Vulnerable, avg2.status);
}

/// Given a package and an `avg::AVG`, returns true if the system is affected
fn system_is_affected(pacman: &alpm::Alpm, pkg: &str, avg: &avg::AVG) -> bool {
    match pacman.query_package_version(pkg.clone()) {
        Ok(v) => {
            info!("Found installed version {} for package {}", v, pkg);
            match avg.fixed {
                Some(ref version) => {
                    info!("Comparing with fixed version {}", version);
                    let cmp = pacman
                        .vercmp(v.clone(), version.clone())
                        .expect("Alpm::vercmp failed");
                    if let Ordering::Less = cmp {
                        return true;
                    }
                }
                None => return true,
            };
        }
        Err(_) => debug!("Package {} not installed", pkg),
    }

    false
}

#[test]
fn test_system_is_affected() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert_eq!(false,
               system_is_affected(&pacman, &"pacman".to_string(), &avg1));

    let avg2 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("7.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert!(system_is_affected(&pacman, &"pacman".to_string(), &avg2));
}

/// Given a list of package names, returns true when at least one is installed
fn package_is_installed(pacman: &alpm::Alpm, packages: &[String]) -> bool {
    for pkg in packages {
        match pacman.query_package_version(pkg.as_str()) {
            Ok(_) => {
                info!("Package {} is installed", pkg);
                return true;
            }
            Err(_) => debug!("Package {} not installed", pkg),
        }
    }
    false
}

#[test]
fn test_package_is_installed() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let packages = vec!["pacman".to_string(), "pac".to_string()];
    assert!(package_is_installed(&pacman, &packages));

    let packages = vec!["pac".to_string()];
    assert_eq!(false, package_is_installed(&pacman, &packages));
}

/// Merge a list of `avg::AVG` into a single `avg::AVG` using major version as version
fn merge_avgs(pacman: &alpm::Alpm,
              cves: &BTreeMap<String, Vec<avg::AVG>>)
              -> BTreeMap<String, avg::AVG> {
    let mut avgs: BTreeMap<String, avg::AVG> = BTreeMap::new();
    for (pkg, list) in cves.iter() {
        let mut avg_issues = vec![];
        let mut avg_fixed: Option<String> = None;
        let mut avg_severity = enums::Severity::Unknown;
        let mut avg_status = enums::Status::Unknown;

        for a in list.iter() {
            avg_issues.append(&mut a.issues.clone());

            match avg_fixed.clone() {
                Some(ref version) => {
                    if let Some(ref v) = a.fixed {
                        let cmp = pacman
                            .vercmp(version.to_string(), v.to_string())
                            .expect("Alpm::vercmp failed");
                        if let Ordering::Greater = cmp {
                            avg_fixed = a.fixed.clone();
                        }
                    }
                }
                None => avg_fixed = a.fixed.clone(),
            }

            if a.severity > avg_severity {
                avg_severity = a.severity;
            }

            // We only care about testing stuff
            if a.status == enums::Status::Testing {
                avg_status = a.status.clone();
            }
        }

        let avg = avg::AVG {
            issues: avg_issues,
            fixed: avg_fixed,
            severity: avg_severity,
            status: avg_status,
        };
        avgs.insert(pkg.to_string(), avg);
    }

    avgs
}

#[test]
fn test_merge_avgs() {
    let mut avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Fixed,
    };

    let avg2 = avg::AVG {
        issues: vec!["CVE-4".to_string(), "CVE-10".to_string()],
        fixed: Some("0.9.8".to_string()),
        severity: enums::Severity::High,
        status: enums::Status::Testing,
    };

    assert!(enums::Severity::Critical > enums::Severity::High);

    avgs.insert("package".to_string(), vec![avg1.clone(), avg2.clone()]);

    avgs.insert("package2".to_string(), vec![avg1, avg2]);

    let pacman = alpm::Alpm::new().expect("Alpm::new failed");
    let merged = merge_avgs(&pacman, &avgs);

    assert_eq!(2, merged.len());
    assert_eq!(4,
               merged
                   .get(&"package".to_string())
                   .expect("'package' key not found")
                   .issues
                   .len());
    assert_eq!(enums::Severity::High,
               merged
                   .get(&"package".to_string())
                   .expect("'package' key not found")
                   .severity);
    assert_eq!(enums::Status::Testing,
               merged
                   .get(&"package".to_string())
                   .expect("'package' key not found")
                   .status);
}

/// Print a list of `avg::AVG`
fn print_avgs(options: &Options, avgs: &BTreeMap<String, avg::AVG>) {
    for (pkg, avg) in avgs {
        match avg.fixed {
            Some(ref v) => {
                if options.quiet >= 2 {
                    println!("{}", pkg);
                } else if options.quiet == 1 {
                    println!("{}>={}", pkg, v);
                } else {
                    match options.format {
                        Some(ref f) => {
                            println!("{}",
                                     f.replace("%n", pkg.as_str())
                                         .replace("%c", avg.issues.iter().join(",").as_str()))
                        }
                        None => {
                            print!("Package {} is affected by {}. {}!",
                                   pkg,
                                   avg.issues.join(", "),
                                   avg.severity);
                            if avg.status == enums::Status::Testing {
                                println!("Update to {} from testing repos!", v)
                            } else {
                                println!("Update to {}!", v)
                            }
                        }
                    }
                }
            }
            None => {
                if !options.upgradable_only {
                    if options.quiet > 0 {
                        println!("{}", pkg);
                    } else {
                        match options.format {
                            Some(ref f) => {
                                println!("{}",
                                         f.replace("%n", pkg.as_str())
                                             .replace("%c",
                                                      avg.issues.iter().join(",").as_str()))
                            }
                            None => {
                                println!("Package {} is affected by {}. {}!",
                                         pkg,
                                         avg.issues.join(", "),
                                         avg.severity);
                            }
                        }
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(docopt_macros)]
#[macro_use]

extern crate log;
extern crate rustc_serialize;
extern crate docopt;
extern crate hyper;
extern crate sqlite3;

mod db;
mod github;
mod model;
mod view;

use std::env;
use std::io::{self, Write};
use std::fs;
use std::path::PathBuf;
use sqlite3::SqliteResult;

const VERSION: &'static str = "0.0.1";

docopt!(Args derive Debug, "
Usage:
  cref
  cref import <repo>
  cref list
  cref update
  cref delete <repo>
  cref (-help | --version)

Options:
  -h, --help     Show this screen
  -v, --version  Show version
");

extern crate env_logger;

fn main() {
    env_logger::init().unwrap();

    let args = Args::docopt().decode().unwrap_or_else(|e| e.exit());

    match run(args) {
        Ok(_) => {},
        Err(e) => abort(e.to_string())
    }
}

pub fn abort(why: String) {
    write!(&mut io::stderr(), "{}", why).unwrap();
    ::std::process::exit(1)
}

fn run(args: Args) -> SqliteResult<()> {
    trace!("{:?}", args);

    create_cref_dir();

    if args.cmd_import { // cref import <repo>
        try!(execute_import(args.arg_repo));
    } else if args.cmd_list { // cref list
        try!(execute_list());
    } else if args.cmd_update { // cref update
        try!(execute_update());
    } else if args.cmd_delete { // cref delete <repo>
        try!(execute_delete(args.arg_repo));
    } else if args.flag_version { // cref -v
        println!("{}", VERSION);
    } else { // cref
        try!(execute());
    }

    Ok(())
}

fn execute_import(repository_name: String) -> SqliteResult<()> {
    let mut db = try!(db::Db::new(db_file()));
    let mut github = github::GitHub::new();
    let commits = github.fetch_commits(&repository_name);
    db.insert_commits(&repository_name, commits)
}

fn execute_list() -> SqliteResult<()> {
    let db = try!(db::Db::new(db_file()));
    let repositories = try!(db.select_repositories());
    repositories.iter().inspect(|repository| {
            println!("{:?}", repository);
        }).collect::<Vec<_>>();
    Ok(())
}

fn execute_update() -> SqliteResult<()> {
    let mut db = try!(db::Db::new(db_file()));
    let mut github = github::GitHub::new();
    try!(db.select_repositories()).iter().map(|repository| {
            let commits = github.fetch_commits(&repository.name);
            db.insert_commits(&repository.name, commits);
        }).collect::<Vec<_>>();
    Ok(())
}

fn execute_delete(repository_name: String) -> SqliteResult<()> {
    let mut db = try!(db::Db::new(db_file()));
    try!(db.delete_repository(repository_name));
    Ok(())
}

fn execute() -> SqliteResult<()> {
    let db = try!(db::Db::new(db_file()));
    let commits = try!(db.select_commits());
    let mut screen = view::Screen::new(commits);
    screen.draw();
    Ok(())
}

fn db_file() -> PathBuf {
    cref_dir().join("cref.db")
}

fn cref_dir() -> PathBuf {
    env::home_dir().map(|p| p.join(".cref")).unwrap()
}

fn create_cref_dir() {
    fs::create_dir(cref_dir());
}
<commit_msg>Enable to receive repository list<commit_after>#![feature(plugin)]
#![plugin(docopt_macros)]
#[macro_use]

extern crate log;
extern crate rustc_serialize;
extern crate docopt;
extern crate hyper;
extern crate sqlite3;

mod db;
mod github;
mod model;
mod view;

use std::env;
use std::io::{self, Write};
use std::fs;
use std::path::PathBuf;
use sqlite3::SqliteResult;

const VERSION: &'static str = "0.0.1";

docopt!(Args derive Debug, "
Usage:
  cref
  cref import <import-repo>...
  cref list
  cref update [<update-repo>...]
  cref delete <delete-repo>
  cref (-help | --version)

Options:
  -h, --help     Show this screen
  -v, --version  Show version
");

extern crate env_logger;

fn main() {
    env_logger::init().unwrap();

    let args = Args::docopt().decode().unwrap_or_else(|e| e.exit());

    match run(args) {
        Ok(_) => {},
        Err(e) => abort(e.to_string())
    }
}

pub fn abort(why: String) {
    write!(&mut io::stderr(), "{}", why).unwrap();
    ::std::process::exit(1)
}

fn run(args: Args) -> SqliteResult<()> {
    trace!("{:?}", args);

    create_cref_dir();

    if args.cmd_import { // cref import <repo>
        try!(execute_import(args.arg_import_repo));
    } else if args.cmd_list { // cref list
        try!(execute_list());
    } else if args.cmd_update { // cref update
        try!(execute_update(args.arg_update_repo));
    } else if args.cmd_delete { // cref delete <repo>
        try!(execute_delete(args.arg_delete_repo));
    } else if args.flag_version { // cref -v
        println!("{}", VERSION);
    } else { // cref
        try!(execute());
    }

    Ok(())
}

fn execute_import(repository_names: Vec<String>) -> SqliteResult<()> {
    let mut db = try!(db::Db::new(db_file()));
    let mut github = github::GitHub::new();
    repository_names.iter().map(|repository_name| {
            let commits = github.fetch_commits(&repository_name);
            db.insert_commits(&repository_name, commits);
        }).collect::<Vec<_>>();
    Ok(())
}

fn execute_list() -> SqliteResult<()> {
    let db = try!(db::Db::new(db_file()));
    let repositories = try!(db.select_repositories());
    repositories.iter().map(|repository| {
            println!("{:?}", repository);
        }).collect::<Vec<_>>();
    Ok(())
}

fn execute_update(repository_names: Vec<String>) -> SqliteResult<()> {
    let update = |repository_names: Vec<String>| {
        match db::Db::new(db_file()) {
            Ok(mut db) => {
                let mut github = github::GitHub::new();

                repository_names.iter().map(|repository_name| {
                        let commits = github.fetch_commits(&repository_name);
                        db.insert_commits(&repository_name, commits);
                    }).collect::<Vec<_>>();
            },
            Err(e) => abort(e.to_string())
        }
    };

    match repository_names.len() {
        0 => {
            let db = try!(db::Db::new(db_file()));
            let all_repository_names = try!(db.select_repositories()).iter().map(|repository| {
                    repository.name.clone()
                }).collect::<Vec<String>>();
            update(all_repository_names);
        },
        _ => {
            update(repository_names);
        }
    }
    Ok(())
}

fn execute_delete(repository_name: String) -> SqliteResult<()> {
    let mut db = try!(db::Db::new(db_file()));
    try!(db.delete_repository(repository_name));
    Ok(())
}

fn execute() -> SqliteResult<()> {
    let db = try!(db::Db::new(db_file()));
    let commits = try!(db.select_commits());
    let mut screen = view::Screen::new(commits);
    screen.draw();
    Ok(())
}

fn db_file() -> PathBuf {
    cref_dir().join("cref.db")
}

fn cref_dir() -> PathBuf {
    env::home_dir().map(|p| p.join(".cref")).unwrap()
}

fn create_cref_dir() {
    fs::create_dir(cref_dir());
}
<|endoftext|>