use std::path::{PathBuf,Path};
use std::str::FromStr;
use std::any::type_name;
use std::fs;
use failure::{Error,err_msg,bail};
use molconv::trajectory::TrajectoryType;
use clap::{App,Arg};
pub struct Config {
pub input: Vec<PathBuf>,
pub output: Option<PathBuf>,
pub first: usize,
pub last: usize,
pub stride: usize,
pub indeces: Vec<usize>,
}
fn file_exists(val: String) -> Result<(), String> {
if Path::new(&val).exists() {
Ok(())
} else {
Err(format!("{} does not exist", val))
}
}
fn is_trajectory_file(val: String) -> Result<(), String> {
let path = Path::new(&val);
match TrajectoryType::from_path(path) {
Ok(_) => Ok(()),
Err(err) => Err(format!("{}", err))
}
}
fn trajectory_exists(val: String) -> Result<(), String> {
file_exists(val.clone())?;
is_trajectory_file(val)
}
fn number<F: FromStr>(val: String) -> Result<(), String> {
if val.parse::<F>().is_ok() {
Ok(())
} else {
Err(format!("Not a {}", type_name::<F>()))
}
}
fn read_index_file(path: &Path) -> Result<Vec<usize>, Error> {
let contents = fs::read_to_string(path)?;
let indeces = contents.split_whitespace()
.map(|s| s.parse::<usize>())
.collect::<Result<Vec<usize>, _>>();
match indeces {
Ok(ind) => Ok(ind),
Err(err) => Err(err_msg(format!("Failed to read index file: {}", err)))
}
}
pub fn parse_args() -> Result<Config, Error> {
let matches = App::new("molconv")
.about("Convert between differnet molecular structure and trjectory formats.")
.after_help("Supported file formats: TRR, XTC")
.version(crate_version!())
.arg(
Arg::with_name("input")
.help("Input trajectory")
.long("input")
.short("f")
.required(true)
.takes_value(true)
.validator(trajectory_exists)
.min_values(1)
)
.arg(
Arg::with_name("output")
.help("Output trajectory")
.long("output")
.short("o")
.takes_value(true)
.validator(is_trajectory_file)
)
.arg(
Arg::with_name("index_file")
.help("Zero based atom indeces separated by whitespace")
.long("indexfile")
.short("i")
.required(false)
.takes_value(true)
.validator(file_exists)
)
.arg(
Arg::with_name("first")
.help("First frame to read")
.long("first")
.default_value("1")
.required(false)
.takes_value(true)
.validator(number::<usize>)
)
.arg(
Arg::with_name("last")
.help("Last frame to read")
.long("last")
.required(false)
.takes_value(true)
.validator(number::<usize>)
)
.arg(
Arg::with_name("stride")
.help("Skip that many frames after each frame")
.long("stride")
.default_value("1")
.required(false)
.takes_value(true)
.validator(number::<usize>)
)
.get_matches();
let input: Vec<PathBuf> = matches.values_of("input").unwrap()
.map(|i| PathBuf::from(i))
.collect();
let output = if matches.is_present("output") {
Some(PathBuf::from(matches.value_of("output").unwrap()))
} else {
None
};
let first = matches.value_of("first").unwrap().parse::<usize>()? -1;
let last = if matches.is_present("last") {
matches.value_of("last").unwrap().parse::<usize>()? - 1
} else {
std::usize::MAX - 1 };
let stride = matches.value_of("stride").unwrap().parse::<usize>()?;
if last < first {
bail!("Last frame cannot be before first frame!")
}
let indeces = if matches.is_present("index_file") {
let index_path = Path::new(matches.value_of("index_file").unwrap());
read_index_file(&index_path)?
} else {
vec![]
};
Ok(Config {
input,
output,
first,
last,
stride,
indeces
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_exists() {
let exist = file_exists(String::from("tests/data/1l2y.trr"));
assert!(exist.is_ok());
let exist = file_exists(String::from("some/random/path"));
assert!(exist.is_err());
}
#[test]
fn test_is_trajectory_file() {
let exist = is_trajectory_file(String::from("some/random/path/1l2y.trr"));
assert!(exist.is_ok());
let exist = is_trajectory_file(String::from("some/random/path"));
assert!(exist.is_err());
}
#[test]
fn test_trajectory_exists() {
let exist = trajectory_exists(String::from("tests/data/1l2y.trr"));
assert!(exist.is_ok());
let exist = trajectory_exists(String::from("some/random/path"));
assert!(exist.is_err());
let exist = trajectory_exists(String::from("/tests/data/1l2y.index"));
assert!(exist.is_err());
}
#[test]
fn test_read_index_file() {
let indeces = read_index_file(Path::new("tests/data/1l2y.index")).unwrap();
let expected_indeces: Vec<usize> = vec![1, 3, 5];
indeces.iter().zip(&expected_indeces).for_each(|(i, e_i)| assert!(i == e_i))
}
}