Skip to content

Commit

Permalink
feat: add list of available profiles to run error
Browse files Browse the repository at this point in the history
  • Loading branch information
MrHedmad committed Jul 11, 2024
1 parent 6cf3840 commit 10d3b85
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 148 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

145 changes: 2 additions & 143 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::options::extract_profile_paths;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::path::PathBuf;

use crate::cache::{check_last_profile, delete_last_profile, get_cache};
use crate::execution::{setup_ctrlc_hook, Executor, FileMover};
Expand All @@ -9,148 +10,6 @@ use crate::utils::update_timestamps;

use anyhow::{anyhow, bail, Result};

/// Push a bit of a string to the end of this path
///
/// Useful if you want to add an extension to the path.
/// Requires a clone.
fn push_fragment(buffer: impl AsRef<Path>, ext: &str) -> PathBuf {
let buffer = buffer.as_ref();
let mut path = buffer.as_os_str().to_owned();
path.push(ext);
path.into()
}

fn infer_test_data(paths: Vec<PathBuf>) -> HashMap<PathBuf, PathBuf> {
let mut matches: HashMap<PathBuf, PathBuf> = HashMap::new();

for path in paths.clone() {
let file_name = path.file_name().unwrap().to_string_lossy();
if file_name.starts_with("test_") {
let slug = file_name.trim_start_matches("test_");
let potential_target = path.clone().with_file_name(slug);
if paths.iter().any(|x| *x == potential_target) {
matches.insert(potential_target, path);
}
}
}

matches
}

// TODO: This checks for the existence of profile paths here. This is a bad
// thing. It's best to handle the error when we actually do the move.
// This was done this way because I want a nice error list.
// The 'check_existence' check was added to overcome this, but it's a hack.
fn extract_profile_paths(
config: &KerblamTomlOptions,
profile_name: &str,
check_existance: bool,
) -> Result<Vec<FileMover>> {
let root_dir = config.input_data_dir();

// If there are no profiles, an empty hashmap is OK intead:
// we can add the default "test" profile anyway.
let mut profiles = {
let data = config.clone().data;
match data {
Some(x) => x.profiles.unwrap_or(HashMap::new()),
None => HashMap::new(),
}
};

// add the default 'test' profile
if !profiles.keys().any(|x| x == "test") {
let input_files = config.input_files();
let inferred_test = infer_test_data(input_files);
if !inferred_test.is_empty() {
log::debug!("Inserted inferred test profile: {inferred_test:?}");
profiles.insert("test".to_string(), inferred_test);
}
}

let profile = profiles
.get(profile_name)
.ok_or(anyhow!("Could not find {} profile", profile_name))?;

// Check if the sources exist, otherwise we crash now, and not later
// when we actually move the files.
let exist_check: Vec<anyhow::Error> = profile
.iter()
.flat_map(|(a, b)| [a, b])
.map(|file| {
let f = &root_dir.join(file);
log::debug!("Checking if {f:?} exists...");
match f.try_exists() {
Ok(i) => {
if i {
Ok(())
} else {
bail!("\t - {file:?} does not exist!")
}
}
Err(e) => bail!("\t- {file:?} - {e:?}"),
}
})
.filter_map(|x| x.err())
.collect();

if !exist_check.is_empty() & check_existance {
let mut missing: Vec<String> = Vec::with_capacity(exist_check.len());
for item in exist_check {
missing.push(item.to_string());
}
bail!(
"Failed to find some profiles files:\n{}",
missing.join("\n")
)
}

// Also check if the targets do NOT exist, so we don't overwrite anything
let exist_check: Vec<anyhow::Error> = profile
.iter()
.flat_map(|(a, b)| [a, b])
.map(|file| {
let f = &root_dir.join(push_fragment(file, ".original"));
log::debug!("Checking if {f:?} destroys files...");
if f.exists() {
bail!("\t- {:?} would be destroyed by {:?}!", f, file)
};
Ok(())
})
.filter_map(|x| x.err())
.collect();

if !exist_check.is_empty() & check_existance {
let mut missing: Vec<String> = Vec::with_capacity(exist_check.len());
for item in exist_check {
missing.push(item.to_string());
}
bail!(
"Some profile temporary files would overwrite real files:\n{}",
missing.join("\n")
)
}

Ok(profile
.iter()
.flat_map(|(original, profile)| {
// We need two FileMovers. One for the temporary file
// that holds the original file (e.g. 'to'), and one for the
// profile-to-original rename.
// To unwind, we just redo the transaction, but in reverse.
[
// This one moves the original to the temporary file
FileMover::from((
&root_dir.join(original),
&root_dir.join(push_fragment(original, ".original")),
)),
// This one moves the profile one to the original one
FileMover::from((&root_dir.join(profile), &root_dir.join(original))),
]
})
.collect())
}

pub fn kerblam_run_project(
config: KerblamTomlOptions,
pipe: Pipe,
Expand Down
137 changes: 134 additions & 3 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use std::io::{self, BufRead};
use std::path::Path;
use std::{collections::HashMap, path::PathBuf};

use anyhow::Result;
use anyhow::{anyhow, bail, Result};
use url::Url;

use crate::execution::Executor;
use crate::utils::{find_files, warn_kerblam_version};
use crate::execution::{Executor, FileMover};
use crate::utils::{find_files, push_fragment, warn_kerblam_version};

// Note: i keep all the fields that are not used to private until we
// actually support their usage.
Expand Down Expand Up @@ -468,3 +468,134 @@ impl KerblamTomlOptions {
find_files(env, None)
}
}

fn infer_test_data(paths: Vec<PathBuf>) -> HashMap<PathBuf, PathBuf> {
let mut matches: HashMap<PathBuf, PathBuf> = HashMap::new();

for path in paths.clone() {
let file_name = path.file_name().unwrap().to_string_lossy();
if file_name.starts_with("test_") {
let slug = file_name.trim_start_matches("test_");
let potential_target = path.clone().with_file_name(slug);
if paths.iter().any(|x| *x == potential_target) {
matches.insert(potential_target, path);
}
}
}

matches
}

// TODO: This checks for the existence of profile paths here. This is a bad
// thing. It's best to handle the error when we actually do the move.
// This was done this way because I want a nice error list.
// The 'check_existence' check was added to overcome this, but it's a hack.
pub fn extract_profile_paths(
config: &KerblamTomlOptions,
profile_name: &str,
check_existance: bool,
) -> Result<Vec<FileMover>> {
let root_dir = config.input_data_dir();

// If there are no profiles, an empty hashmap is OK intead:
// we can add the default "test" profile anyway.
let mut profiles = {
let data = config.clone().data;
match data {
Some(x) => x.profiles.unwrap_or(HashMap::new()),
None => HashMap::new(),
}
};

// add the default 'test' profile
if !profiles.keys().any(|x| x == "test") {
let input_files = config.input_files();
let inferred_test = infer_test_data(input_files);
if !inferred_test.is_empty() {
log::debug!("Inserted inferred test profile: {inferred_test:?}");
profiles.insert("test".to_string(), inferred_test);
}
}

let profile = profiles
.get(profile_name)
.ok_or(anyhow!("Could not find {} profile", profile_name))?;

// Check if the sources exist, otherwise we crash now, and not later
// when we actually move the files.
let exist_check: Vec<anyhow::Error> = profile
.iter()
.flat_map(|(a, b)| [a, b])
.map(|file| {
let f = &root_dir.join(file);
log::debug!("Checking if {f:?} exists...");
match f.try_exists() {
Ok(i) => {
if i {
Ok(())
} else {
bail!("\t - {file:?} does not exist!")
}
}
Err(e) => bail!("\t- {file:?} - {e:?}"),
}
})
.filter_map(|x| x.err())
.collect();

if !exist_check.is_empty() & check_existance {
let mut missing: Vec<String> = Vec::with_capacity(exist_check.len());
for item in exist_check {
missing.push(item.to_string());
}
bail!(
"Failed to find some profiles files:\n{}",
missing.join("\n")
)
}

// Also check if the targets do NOT exist, so we don't overwrite anything
let exist_check: Vec<anyhow::Error> = profile
.iter()
.flat_map(|(a, b)| [a, b])
.map(|file| {
let f = &root_dir.join(push_fragment(file, ".original"));
log::debug!("Checking if {f:?} destroys files...");
if f.exists() {
bail!("\t- {:?} would be destroyed by {:?}!", f, file)
};
Ok(())
})
.filter_map(|x| x.err())
.collect();

if !exist_check.is_empty() & check_existance {
let mut missing: Vec<String> = Vec::with_capacity(exist_check.len());
for item in exist_check {
missing.push(item.to_string());
}
bail!(
"Some profile temporary files would overwrite real files:\n{}",
missing.join("\n")
)
}

Ok(profile
.iter()
.flat_map(|(original, profile)| {
// We need two FileMovers. One for the temporary file
// that holds the original file (e.g. 'to'), and one for the
// profile-to-original rename.
// To unwind, we just redo the transaction, but in reverse.
[
// This one moves the original to the temporary file
FileMover::from((
&root_dir.join(original),
&root_dir.join(push_fragment(original, ".original")),
)),
// This one moves the profile one to the original one
FileMover::from((&root_dir.join(profile), &root_dir.join(original))),
]
})
.collect())
}
26 changes: 25 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,22 @@ pub fn find_pipe_by_name(config: &KerblamTomlOptions, pipe_name: Option<String>)
pipes_list.reverse();

let pipes_list = pipes_list.join("\n");
let profiles: Option<Vec<String>> = config
.clone()
.data
.and_then(|x| x.profiles)
.and_then(|x| Some(x.into_keys().collect()));
let profiles_list = match profiles {
None => "No profiles defined".to_string(),
Some(list) => list.join(", "),
};

let pipe_name = match pipe_name {
None => bail!("No runtime specified. Available runtimes:\n{}", pipes_list),
None => bail!(
"No runtime specified. Available runtimes:\n{}\nAvailable profiles: {}.",
pipes_list,
profiles_list
),
Some(name) => name,
};

Expand Down Expand Up @@ -497,3 +510,14 @@ pub fn update_timestamps(path: &PathBuf) -> anyhow::Result<()> {
log::debug!("Re-touched {files_touched} files.");
Ok(())
}

/// Push a bit of a string to the end of this path
///
/// Useful if you want to add an extension to the path.
/// Requires a clone.
pub fn push_fragment(buffer: impl AsRef<Path>, ext: &str) -> PathBuf {
let buffer = buffer.as_ref();
let mut path = buffer.as_os_str().to_owned();
path.push(ext);
path.into()
}

0 comments on commit 10d3b85

Please sign in to comment.