use std::path::{Path, PathBuf};
use strsim::normalized_levenshtein;
use std::fs::File;
use std::io::*;
use std::collections::HashMap;
use colored::*;
use reqwest;
use serde::{Serialize, Deserialize};
use prettytable::{Table, Row, Cell};
#[derive(Serialize, Deserialize, Debug)]
pub struct FileRes {
name: String,
download_url: Option<String>,
}
pub fn http_get(url: &str) -> String {
let response = reqwest::get(url)
.expect("Error: Url Not Found")
.text()
.expect("Error: Text unextractable from url");
return response;
}
pub fn build_file_map(res: &str) -> HashMap<String, String> {
let all_files: Vec<FileRes> = serde_json::from_str(res).unwrap();
let gitignore_files: Vec<&FileRes> = all_files
.iter()
.filter(|file| file.name.contains("gitignore"))
.collect();
let destructured: Vec<(String, String)> = gitignore_files
.iter()
.map(|file| destructure_to_tup(file))
.collect();
let file_map: HashMap<String, String> = destructured
.into_iter()
.collect();
return file_map;
}
pub fn destructure_to_tup(file_struct: &FileRes) -> (String, String) {
let name:String = file_struct.name
.clone()
.replace(".gitignore", "")
.to_lowercase();
let mut url:String = String::from("");
if let Some(download_url) = &file_struct.download_url {
url.push_str(download_url);
}
return (name, url);
}
pub fn get_raw_ignore_file(file_map: &HashMap<String, String>, lang: &str) -> String {
let mut response: String = String::from("");
let file_url: Option<&String> = file_map.get(lang);
if let Some(file) = file_url {
response.push_str(&http_get(&file));
}
return response;
}
fn format_gitignore(body : &String, language: &str) -> String {
let ignore_template: String = format!("# {} gitignore generated by Blindfold\n\n{}\n\n",
language.to_uppercase(),
body);
println!("Generated .gitignore for {} 🔧", language.magenta().bold());
return ignore_template;
}
pub fn generate_gitignore_file(languages: Vec<&str>, file_map: &HashMap<String, String>) -> String {
let mut gitignore: String = String::from("");
for language in languages.iter() {
if language == &"" {
continue;
}
if file_map.contains_key(&language.to_string()) {
let ignore_body: String = get_raw_ignore_file(&file_map, language);
gitignore.push_str(&format_gitignore(&ignore_body, language));
}
else {
let stdio = stdin();
let input = stdio.lock();
let output = stdout();
let most_similar: Option<String> = suggest_most_similar(input,
output,
language.clone(),
file_map.clone());
if let Some(language) = most_similar {
let ignore_body: String = get_raw_ignore_file(&file_map, &language);
gitignore.push_str(&format_gitignore(&ignore_body, &language));
}
}
}
return gitignore;
}
pub fn suggest_most_similar<R, W>(mut reader: R,
mut writer: W,
typo: &str,
file_map: HashMap<String, String>) -> Option<String>
where
R: BufRead,
W: Write,
{
let mut max: f64 = 0.0;
let mut most_similar: String = String::new();
for candidate in file_map.keys() {
let similarity: f64 = normalized_levenshtein(typo, candidate);
if similarity > max {
most_similar = candidate.to_string();
max = similarity;
}
}
write!(&mut writer, "Couldn't generate template for {}, did you mean {}? [y/N]: ",
typo.yellow().bold(),
most_similar.bright_green().bold()).expect("Unable to write");
stdout().flush().ok();
let mut choice: String = String::new();
reader.read_line(&mut choice)
.ok()
.expect("Couldn't read line");
if choice.to_lowercase().trim() == String::from("y") {
return Some(most_similar);
}
return None;
}
pub fn write_to_file(dest: &str, gitignore: String) -> std::io::Result<()> {
let filepath: PathBuf = Path::new(dest).join(".gitignore");
println!("Writing file to {}... ✏️", format!("{}.gitignore", dest)
.bright_blue()
.bold());
let mut file = File::create(filepath)?;
file.write_all(gitignore.as_bytes())?;
println!("{} ✨", "Done!".green().bold());
Ok(())
}
pub fn append_to_file(destination: &str, gitignore: String) -> std::io::Result<()> {
let filepath: PathBuf = Path::new(destination).join(".gitignore");
let mut file = File::open(filepath)?;
let mut existing: String= String::new();
file.read_to_string(&mut existing)?;
let combined: String = format!("{}{}", existing, gitignore);
if !combined.is_empty() {
println!("Loaded existing gitignore file from {} 💾", format!("{}.gitignore", destination)
.bright_blue()
.bold());
write_to_file(destination, combined).expect("Could'nt write to file ⚠️ ");
}
return Ok(());
}
pub fn list_templates(file_map: HashMap<String, String>) {
let mut table = Table::new();
let mut keys: Vec<String> = file_map
.keys()
.map(|key| key.clone())
.collect();
keys.sort();
let mut chunks = keys.chunks(4);
while let Some(chunk) = chunks.next() {
let cells = chunk
.iter()
.map(|item| Cell::new(item))
.collect();
let row = Row::new(cells);
table.add_row(row);
}
table.printstd();
}