runner 0.7.0

Utility for running Rust snippets
use crate::cache;
use crate::crate_utils;
use crate::state::State;
use anyhow::{bail, Context, Result};

use crate::strutil::{split, word_after};
use std::collections::HashSet;
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
use std::io::IsTerminal;
use std::path::{Path, PathBuf};
use std::process;

fn simplify_qualified_names(text: &str) -> String {
    let std = "std::";
    let mut res = String::new();
    let mut s = text;
    while let Some(pos) = s.find(std) {
        res.push_str(&s[0..pos]);
        s = &s[pos + std.len()..];
        if let Some(pos) = s.find("::") {
            s = &s[pos + 2..];
        }
    }
    res.push_str(s);
    res
}

// handle two useful cases:
// - compile a crate as a dynamic library, given a name and an output dir
// - compile a program, given a program
pub fn compile_crate(
    state: &State,
    crate_name: &str,
    crate_path: &Path,
    output_program: Option<&Path>,
    mut extern_crates: Vec<String>,
    features: Vec<String>,
) -> Result<bool> {
    let verbose = state.verbose;
    let simplify = state.simplify;
    let debug = !state.optimize;

    // implicit linking works fine, until it doesn't
    extern_crates.extend(state.externs.clone());
    extern_crates.sort();
    extern_crates.dedup();
    // libc is such a special case
    if state.libc {
        extern_crates.push("libc".into());
    }
    let mut cfg = state.cfg.clone();
    let explicit_features = state.features.clone();
    for f in if explicit_features.len() > 0 {
        explicit_features
    } else {
        features
    } {
        cfg.push(format!("feature=\"{}\"", f));
    }
    let cache = cache::get_cache(&state)?;
    let mut builder = process::Command::new("rustc");
    if state.edition != "2015" {
        builder.args(&["--edition", &state.edition]);
    }
    if !state.build_static {
        // stripped-down dynamic link
        builder
            .args(&["-C", "prefer-dynamic"])
            .args(&["-C", "debuginfo=0"]);
        if let Some(link) = &state.link {
            if verbose {
                println!("linking against {}", link);
            }
            builder.arg("-L").arg(link);
        }
    } else {
        // static build
        builder.arg(if state.optimize { "-O" } else { "-g" });
        if state.optimize {
            // TODO but stacktraces??
            // no point in carrying around all that baggage...
            builder.args(&["-C", "debuginfo=0"]);
        }
    }
    // implicitly linking against crates in the dynamic or static cache
    builder.arg("-L").arg(&cache);
    if !state.exe {
        // as a dynamic library
        builder
            .args(&["--crate-type", "dylib"])
            .arg("--out-dir")
            .arg(&cache)
            .arg("--crate-name")
            .arg(&crate_utils::proper_crate_name(crate_name));
    } else {
        builder.arg("-o").arg(output_program.unwrap());
    }
    for c in cfg {
        builder.arg("--cfg").arg(&c);
    }

    // explicit --extern references require special treatment for
    // static builds, since the libnames include a hash.
    // So we look for the latest crate of this name
    let extern_crates: Vec<(String, String)> = if state.build_static && extern_crates.len() > 0 {
        let m = cache::get_metadata()?;
        let result = extern_crates
            .into_iter()
            .map(|c| {
                Ok::<(String, String), String>((
                    m.get_full_crate_name(&c, debug).ok_or_else(|| c.clone())?,
                    c,
                ))
            })
            .collect::<Vec<_>>();
        let (mut crates, mut errors) = (vec![], vec![]);
        for r in result {
            match r {
                Ok(c) => crates.push(c),
                Err(e) => errors.push(e),
            }
        }
        if errors.len() > 0 {
            cache::save_missing_crates(&errors)?;
            bail!(
                "no such crates '{}' in static cache: use --add . to add them",
                errors.join(" ")
            );
        }
        if verbose {
            println!("{:?} crates", crates);
        }
        crates
    } else {
        extern_crates
            .into_iter()
            .map(|c| (format!("{}{}{}", DLL_PREFIX, c, DLL_SUFFIX), c))
            .collect()
    };

    for (name, c) in extern_crates {
        let full_path = PathBuf::from(&cache).join(&name);
        let ext = format!("{}={}", c, full_path.display());
        if verbose {
            println!("extern {}", ext);
        }
        builder.arg("--extern").arg(&ext);
    }
    builder.arg(crate_path);
    if simplify {
        if std::io::stderr().is_terminal() {
            builder.args(&["--color", "always"]);
        }
        let output = builder.output().context("can't run rustc")?;
        let status = output.status.success();
        if !status {
            let err = String::from_utf8_lossy(&output.stderr);
            eprintln!("{}", simplify_qualified_names(&err));
        }
        Ok(status)
    } else {
        Ok(builder.status().context("can't run rustc")?.success())
    }
}

pub fn massage_snippet(
    code: String,
    prelude: String,
    extern_crates: Vec<String>,
    wild_crates: Vec<String>,
    macro_crates: HashSet<String>,
    body_prelude: String,
) -> Result<(String, Vec<String>)> {
    use crate::strutil::{after, word_after};

    fn indent_line(line: &str) -> String {
        format!("    {}\n", line)
    }

    let mut prefix = prelude;
    let mut crate_begin = String::new();
    let mut body = String::new();
    let mut deduced_externs = Vec::new();

    body += &body_prelude;
    if extern_crates.len() > 0 {
        let aliases = cache::get_aliases()?;
        for c in &extern_crates {
            prefix += &if let Some(aliased) = aliases.get(c) {
                format!("extern crate {} as {};\n", aliased, c)
            } else {
                let mac = if macro_crates.contains(c) {
                    "#[macro_use] "
                } else {
                    ""
                };
                format!("{}extern crate {};\n", mac, c)
            };
        }
        for c in wild_crates {
            prefix += &format!("use {}::*;\n", c);
        }
    }
    let mut lines = code.lines();
    let mut first = true;
    for line in lines.by_ref() {
        let line = line.trim_start();
        if first {
            // files may start with #! shebang or comment...
            if line.starts_with("#!/") || line.starts_with("//") {
                continue;
            }
            first = false;
        }
        // crate import, use should go at the top.
        // Particularly need to force crate-level attributes to the top
        // These must not be in the `run` function we're generating
        if let Some(rest) = after(line, "#[macro_use") {
            if let Some(crate_name) = word_after(rest, "extern crate ") {
                deduced_externs.push(crate_name);
            }
            prefix += line;
            prefix.push('\n');
        } else if line.starts_with("extern ") || line.starts_with("use ") {
            extract_externs(line, &mut deduced_externs);
            prefix += line;
            prefix.push('\n');
        } else if line.starts_with("#![") {
            // inner attributes really need to be at the top of the file
            crate_begin += line;
            crate_begin.push('\n');
        } else if line.len() > 0 {
            body += &indent_line(line);
            break;
        }
    }
    // and indent the rest!
    body.extend(lines.map(indent_line));

    deduced_externs.extend(extern_crates);
    deduced_externs.sort();
    deduced_externs.dedup();

    let massaged_code = format!(
        "{}
{}

fn run(args: Vec<String>) -> std::result::Result<(),Box<dyn std::error::Error+Sync+Send>> {{
{}    Ok(())
}}
fn main() {{
    if let Err(e) = run(std::env::args().collect()) {{
        println!(\"error: {{:?}}\",e);
    }}
}}
",
        crate_begin, prefix, body
    );

    Ok((massaged_code, deduced_externs))
}

pub fn extract_externs(line: &str, deduced_externs: &mut Vec<String>) -> bool {
    if let Some(crate_name) = word_after(line, "extern crate ") {
        deduced_externs.push(crate_name);
        return true;
    }
    if let Some(path) = word_after(line, "use ") {
        let (name, rest) = split(&path, ':');
        if !["std", "core", "alloc", "crate"].contains(&name) || rest == "" {
            deduced_externs.push(name.into());
            return true;
        }
    }
    false
}