use std::{
fs::File,
io::{BufRead, BufReader},
path::{Path, PathBuf},
};
use crate::{
utils::expression,
utils::variable_data::{GOAT_ASSEMBLY_VARIABLE_DATA, GOAT_TAXON_VARIABLE_DATA},
IndexType, UPPER_CLI_FILE_LIMIT,
};
use anyhow::{bail, Context, Result};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
pub fn generate_unique_strings(
matches: &clap::ArgMatches,
index_type: IndexType,
) -> Result<Vec<String>> {
let tax_name_op = matches.get_one::<String>("taxon");
let filename_op = matches.get_one::<PathBuf>("file");
let print_expression = matches.get_one::<bool>("print-expression");
if let Some(p) = print_expression {
if *p {
match index_type {
IndexType::Taxon => expression::print_variable_data(&*GOAT_TAXON_VARIABLE_DATA),
IndexType::Assembly => {
expression::print_variable_data(&*GOAT_ASSEMBLY_VARIABLE_DATA)
}
}
std::process::exit(0);
}
}
let url_vector: Vec<String>;
match tax_name_op {
Some(s) => {
if s.is_empty() {
bail!("Empty string found, please specify a taxon.");
}
url_vector = parse_comma_separated(s);
}
None => match filename_op {
Some(s) => {
url_vector = lines_from_file(s)?;
if url_vector.len() > *UPPER_CLI_FILE_LIMIT {
let limit_string = pretty_print_usize(*UPPER_CLI_FILE_LIMIT);
bail!("Number of taxa specified cannot exceed {}.", limit_string)
}
}
None => bail!("One of -f (--file) or -t (--taxon) should be specified."),
},
}
let url_vector_len = url_vector.len();
let mut chars_vec = vec![];
for _ in 0..url_vector_len {
let mut rng = thread_rng();
let chars: String = (0..15).map(|_| rng.sample(Alphanumeric) as char).collect();
chars_vec.push(chars.clone());
}
Ok(chars_vec)
}
pub fn lines_from_file(filename: impl AsRef<Path>) -> Result<Vec<String>> {
let file = File::open(&filename)
.with_context(|| format!("Could not open {:?}", filename.as_ref().as_os_str()))?;
let buf = BufReader::new(file);
let buf_res: Result<Vec<String>> = buf
.lines()
.map(|l| {
l.with_context(|| {
format!(
"Error in mapping buf_lines from {:?}",
filename.as_ref().as_os_str()
)
})
})
.collect();
buf_res
}
pub fn parse_comma_separated(taxids: &str) -> Vec<String> {
let res: Vec<&str> = taxids.split(',').collect();
let mut res2 = Vec::new();
for mut str in res {
while str.ends_with(' ') {
let len = str.len();
let new_len = len.saturating_sub(" ".len());
str = &str[..new_len];
}
let mut index = 0;
while str.starts_with(' ') {
index += 1;
str = &str[index..];
}
let replaced = str.replace('\"', "").replace('\'', "");
res2.push(replaced);
}
res2.sort_unstable();
res2.dedup();
res2
}
pub fn get_rank_vector(r: &str) -> Vec<String> {
let ranks = vec![
"subspecies".to_string(),
"species".to_string(),
"genus".to_string(),
"family".to_string(),
"order".to_string(),
"class".to_string(),
"phylum".to_string(),
"kingdom".to_string(),
"superkingdom".to_string(),
];
let position_selected = ranks.iter().position(|e| e == r);
match position_selected {
Some(p) => ranks[p..].to_vec(),
None => vec!["".to_string()],
}
}
pub fn format_tsv_output(awaited_fetches: Vec<Result<String, anyhow::Error>>) -> Result<()> {
let mut headers = Vec::new();
for el in &awaited_fetches {
let tsv = match el {
Ok(ref e) => e,
Err(e) => bail!("{}", e),
};
headers.push(tsv.split('\n').next());
}
let header = headers.iter().fold(headers[0], |acc, &item| {
let acc = acc?;
let item = item?;
if item.len() > acc.len() {
Some(item)
} else {
Some(acc)
}
});
match header {
Some(h) => println!("{}", h),
None => bail!("No header found."),
}
for el in awaited_fetches {
let tsv = match el {
Ok(ref e) => e,
Err(e) => bail!("{}", e),
};
let tsv_iter = tsv.split('\n');
for row in tsv_iter.skip(1) {
println!("{}", row)
}
}
Ok(())
}
pub fn some_kind_of_uppercase_first_letter(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}
pub fn pretty_print_usize(i: usize) -> String {
let mut s = String::new();
let i_str = i.to_string();
let a = i_str.chars().rev().enumerate();
for (idx, val) in a {
if idx != 0 && idx % 3 == 0 {
s.insert(0, ',');
}
s.insert(0, val);
}
s.to_string()
}
pub fn switch_string_to_url_encoding(string: &str) -> Result<&str> {
let res = match string {
"!=" => "!%3D",
"<" => "%3C",
"<=" => "<%3D",
"=" => "%3D",
"==" => "%3D%3D",
">" => "%3E",
">=" => ">%3D",
_ => bail!("Should not reach here."),
};
Ok(res)
}
pub fn did_you_mean(possibilities: &[String], tried: &str) -> Option<String> {
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|word| {
let edit_distance = levenshtein_distance(&word.to_lowercase(), &tried.to_lowercase());
(edit_distance, word.to_owned())
})
.collect();
possible_matches.sort();
if let Some((_, first)) = possible_matches.into_iter().next() {
Some(first)
} else {
None
}
}
fn levenshtein_distance(a: &str, b: &str) -> usize {
let mut result = 0;
if a == b {
return result;
}
let length_a = a.chars().count();
let length_b = b.chars().count();
if length_a == 0 {
return length_b;
}
if length_b == 0 {
return length_a;
}
let mut cache: Vec<usize> = (1..).take(length_a).collect();
let mut distance_a;
let mut distance_b;
for (index_b, code_b) in b.chars().enumerate() {
result = index_b;
distance_a = index_b;
for (index_a, code_a) in a.chars().enumerate() {
distance_b = if code_a == code_b {
distance_a
} else {
distance_a + 1
};
distance_a = cache[index_a];
result = if distance_a > result {
if distance_b > result {
result + 1
} else {
distance_b
}
} else if distance_b > distance_a {
distance_a + 1
} else {
distance_b
};
cache[index_a] = result;
}
}
result
}