use std::{borrow::Cow, env, fs, io, path::PathBuf, process::Command, ffi::OsString, ops::Index};
use lazy_static::lazy_static;
use tempfile::{TempDir, TempPath, NamedTempFile};
use crate::BenchSpec;
const BEGIN_TMPL_VAR: &str = "@\"";
const BEGIN_TMPL_VAR_LEN: usize = BEGIN_TMPL_VAR.len();
const END_TMPL_VAR: &str = "\"@";
const END_TMPL_VAR_LEN: usize = END_TMPL_VAR.len();
const TEMP_PREFIX: &str = concat!(env!("CARGO_PKG_NAME"), "__");
const ENVVAR_PREFIX: Cow<str> = Cow::Borrowed("CRITERION_");
lazy_static! {
static ref CARGO_TARGET_TMPDIR: PathBuf = {
let exe_path = env::current_exe().ok();
if let Some(mut target_tmp_path) = exe_path {
target_tmp_path.pop();
if target_tmp_path.ends_with("deps") {
target_tmp_path.pop();
}
if target_tmp_path.parent().map_or(false, |p| p.ends_with("target")) {
target_tmp_path.pop();
}
target_tmp_path.push("tmp");
if !target_tmp_path.try_exists().expect("Can't check existence of target tmpdir") {
fs::create_dir(&target_tmp_path).expect("Can't create target tmpdir");
}
target_tmp_path
} else {
env::temp_dir()
}
};
}
pub fn make_tempfile<'a, I>(extension: &str, content: I) -> TempPath
where
I: IntoIterator<Item = &'a str>,
{
use io::Write;
let mut file = make_tempfile_writer(extension);
for segment in content {
file.write_all(segment.as_bytes()).unwrap();
}
file.into_temp_path()
}
pub fn make_spec_tempfile(extension: &str, harness: &str, spec: &BenchSpec) -> TempPath {
let mut file = make_tempfile_writer(extension);
process_harness(&mut file, harness, spec)
.expect("failed writing harness tempfile");
file.into_temp_path()
}
pub fn make_empty_tempfile(extension: &str) -> TempPath {
make_tempfile(extension, [])
}
pub fn make_tempfile_writer(extension: &str) -> NamedTempFile {
tempfile::Builder::new()
.prefix(TEMP_PREFIX)
.suffix(extension)
.tempfile_in(&*CARGO_TARGET_TMPDIR)
.unwrap()
}
pub fn make_tempdir() -> TempDir {
tempfile::Builder::new()
.prefix(TEMP_PREFIX)
.tempdir_in(&*CARGO_TARGET_TMPDIR)
.unwrap()
}
pub fn format_command(command: &Command) -> String {
use std::fmt::Write;
let mut command_line = command.get_program().to_string_lossy().into_owned();
let args = command.get_args().map(|a| a.to_string_lossy());
for arg in args {
write!(command_line, " {}", shell_quote(&arg)).unwrap();
}
command_line
}
pub fn reindent(s: &str, target_indent: usize) -> Cow<str> {
use std::fmt::Write;
let non_blank_lines = s.lines().filter(|l| !l.trim_start_matches(|c| char::is_ascii_whitespace(&c)).is_empty());
let mut remove_indent = non_blank_lines.map(|l| l.len() - l.trim_start_matches(' ').len()).min().unwrap_or_default();
let add_indent = if remove_indent >= target_indent {
remove_indent -= target_indent;
0
} else {
target_indent
};
if remove_indent > 0 || add_indent > 0 {
let mut outdented_s = " ".repeat(add_indent);
for line in s.lines() {
let outdented_line = if line.len() > remove_indent {
&line[remove_indent..]
} else { line };
writeln!(outdented_s, "{}", outdented_line).unwrap();
}
outdented_s.into()
} else {
s.into()
}
}
pub fn shell_quote(s: &str) -> Cow<str> {
fn is_shell_unsafe(s: &str) -> bool {
const SAFE_SHELL_CHARS: &str = "@%+=:,./-_";
fn is_char_unsafe(c: char) -> bool {
let is_safe = SAFE_SHELL_CHARS.contains(c) || char::is_ascii_alphanumeric(&c);
!is_safe
}
s.contains(is_char_unsafe)
}
let mut s = Cow::from(s);
if is_shell_unsafe(&s) {
let s_mut = s.to_mut();
if s_mut.contains('\'') {
*s_mut = s_mut.replace('\'', "'\"'\"'");
}
s_mut.insert(0, '\'');
s_mut.push('\'');
}
s
}
pub fn get_criterion_env_var(name: &str, default: &str) -> OsString {
debug_assert!(name.chars().all(|c| !c.is_ascii_lowercase()));
let var_name = ENVVAR_PREFIX + name;
env::var_os(&*var_name)
.unwrap_or_else(|| OsString::from(default))
}
pub fn process_harness<'a, W, V>(out: &mut W, template: &'a str, vars: &V) -> io::Result<()>
where
W: io::Write,
V: Index<&'a str, Output = str>
{
let mut remainder = template;
while let Some(var_start) = remainder.find(BEGIN_TMPL_VAR) {
let var_name_start = var_start + BEGIN_TMPL_VAR_LEN;
let var_name_len = remainder[var_name_start..].find(END_TMPL_VAR)
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
let var_name_end = var_name_start + var_name_len;
let var_name = &remainder[var_name_start..var_name_end];
let var_value = &vars[var_name];
let line_until_var = remainder[..var_start].rsplit('\n').next().unwrap();
let var_indent = line_until_var.len() - line_until_var.trim_start_matches(' ').len();
let preceding = &remainder[..(var_start - var_indent)];
let value_indented = reindent(var_value, var_indent);
write!(out, "{preceding}{value_indented}")?;
let var_end = var_name_end + END_TMPL_VAR_LEN;
remainder = &remainder[var_end..];
}
write!(out, "{remainder}")
}