├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── banner.png ├── demo.gif └── logo.png ├── renovate.json ├── requirements.md └── src ├── lib.rs ├── main.rs └── test.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # RUST gitignore generated by Blindfold 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | /target/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | 15 | # MACOS gitignore generated by Blindfold 16 | 17 | # General 18 | .DS_Store 19 | .AppleDouble 20 | .LSOverride 21 | 22 | # Icon must end with two \r 23 | Icon 24 | 25 | # Thumbnails 26 | ._* 27 | 28 | # Files that might appear in the root of a volume 29 | .DocumentRevisions-V100 30 | .fseventsd 31 | .Spotlight-V100 32 | .TemporaryItems 33 | .Trashes 34 | .VolumeIcon.icns 35 | .com.apple.timemachine.donotpresent 36 | 37 | # Directories potentially created on remote AFP share 38 | .AppleDB 39 | .AppleDesktop 40 | Network Trash Folder 41 | Temporary Items 42 | .apdisk 43 | 44 | 45 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blindfold" 3 | version = "1.0.7" 4 | authors = ["Eóin McMahon "] 5 | edition = "2018" 6 | description ="⚙️ gitignore file generator written in rust" 7 | repository = "https://github.com/Eoin-McMahon/blindfold" 8 | readme="README.md" 9 | license-file="LICENSE" 10 | keywords=["gitignore", "git", "generator"] 11 | categories=["development-tools"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | reqwest = "0.9.0" # http requests 17 | serde = {version = "1.0.111", features = ["derive"]} # serialization 18 | serde_json = "1.0.53" # serialize JSON 19 | clap = "2.33.1" # argument parsing 20 | colored = "1.9" # color in terminal 21 | strsim = "0.10.0" # string similarity metrics 22 | prettytable-rs = "^0.8" # Print pretty tables for list subcommand 23 | itertools = "^0.8" # Coalesce globstars 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Eoin McMahon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | banner 3 |
4 | Logo courtesy of Dominic Houston-Watt 5 |

6 | 7 | 8 | [![Build](https://github.com/Eoin-McMahon/blindfold/workflows/Build/badge.svg)](https://github.com/Eoin-McMahon/blindfold/actions?query=workflow%3ABuild) 9 | [![Crates.io](https://img.shields.io/crates/d/blindfold?color=d)](https://crates.io/crates/blindfold) 10 | [![GitHub license](https://img.shields.io/github/license/Eoin-McMahon/Blindfold)](https://github.com/Eoin-McMahon/Blindfold/blob/master/license.txt) 11 | [![GitHub stars](https://img.shields.io/github/stars/Eoin-McMahon/Blindfold)](https://github.com/Eoin-McMahon/Blindfold/stargazers) 12 | 13 | ## ✨ Features 14 | * Pulls .gitignore templates from gitignore.io. 15 | * Clean and simple CLI 16 | * Suggestion system to help correct potential typos 17 | * Allows for the combination of any number of different templates all into one gitignore 18 | * Allows for appending to pre-existing gitignore templates so that custom directories are not overridden. 19 | * Allows for hosting languages inside directories, so that multiple languages can be neatly split up. 20 | 21 | ## 📦 Installation 22 | NOTE: Rust must be installed on your system for this to work. (Install Rust) 23 | 24 | #### 📥 Download from crates.io 25 | 26 | ```bash 27 | cargo install blindfold 28 | ``` 29 | 30 | #### 🏗️ Build from source 31 | ```bash 32 | git clone https://github.com/Eoin-McMahon/blindfold.git 33 | cd blindfold 34 | cargo install --path ./ 35 | ``` 36 | 37 | This will install the binary and add it to your path. Once installed you can use the tool as shown in the examples below. 38 | ## 🔨 Demo: 39 | 40 | ![demo_video](https://raw.githubusercontent.com/Eoin-McMahon/Blindfold/master/assets/demo.gif) 41 | 42 | ## 🔧 Examples of use: 43 | ```bash 44 | # generates a single gitignore file for both dart and flutter in ./src/.gitignore 45 | blindfold --lang dart flutter 46 | ``` 47 | 48 | ```bash 49 | # use the append flag to add to the pre-existing gitignore file (can be shortened to -a) 50 | blindfold --append macos 51 | ``` 52 | 53 | ```bash 54 | # you can specify a specific destination to store the gitignore file using the dest argument 55 | blindfold --lang rust --dest ./src/ 56 | ``` 57 | 58 | ```bash 59 | # you can put languages into directories by prefixing the language name with the path (which can include '**') 60 | blindfold --lang rs/rust py/python **/make 61 | ``` 62 | 63 | ```bash 64 | # arguments can also be written in shorthand 65 | blindfold -l rust -d ./src/ 66 | ``` 67 | 68 | ```bash 69 | # shows full list of available templates 70 | blindfold list 71 | ``` 72 | 73 | ```bash 74 | # There is a help screen that can be shown which details the subcommands and arguments to supply to the program 75 | blindfold -h 76 | ``` 77 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eoin-McMahon/blindfold/601713f8b5413294d344ef2007e2fcc02d7f8271/assets/banner.png -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eoin-McMahon/blindfold/601713f8b5413294d344ef2007e2fcc02d7f8271/assets/demo.gif -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eoin-McMahon/blindfold/601713f8b5413294d344ef2007e2fcc02d7f8271/assets/logo.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /requirements.md: -------------------------------------------------------------------------------- 1 | # Requirements for IgnoriGen CLI tool 2 | 3 | - [x] Must be able to pass a langauge as a command line argument 4 | - [x] Must be able to pass multiple languages and have each of their gitignores appended to one another 5 | - [x] Must store the gitignore in the running directory or a specified directory through a --destination argument 6 | - [x] Must have a help flag which lists all available templates 7 | - [x] Must show suggestions if an invalid language was passed (i.e Did you mean "flutter"?) 8 | - [x] Must have an append feature 9 | 10 | - [ ] Should have a caching option to save templates. 11 | 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use colored::*; 2 | use prettytable::{Cell, Row, Table}; 3 | use reqwest; 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::HashMap; 6 | use std::fs::File; 7 | use std::io::*; 8 | use std::path::{Path, PathBuf}; 9 | use itertools::Itertools; 10 | use strsim::normalized_levenshtein; 11 | 12 | static GLOBSTAR: &str = "**"; 13 | 14 | #[derive(Serialize, Deserialize, Debug)] 15 | pub struct FileRes { 16 | name: String, 17 | download_url: Option, 18 | } 19 | 20 | // performs a http GET request using the reqwest crate 21 | pub fn http_get(url: &str) -> String { 22 | let response = reqwest::get(url) 23 | .expect("Error: Url Not Found") 24 | .text() 25 | .expect("Error: Text unextractable from url"); 26 | 27 | return response; 28 | } 29 | 30 | // builds a mapping of template names to urls to download them 31 | pub fn build_file_map(res: &str) -> HashMap { 32 | // parse json response to extract name and download link into FileRes struct 33 | let all_files: Vec = serde_json::from_str(res).unwrap(); 34 | 35 | // filter out non-gitignore files 36 | let gitignore_files: Vec<&FileRes> = all_files 37 | .iter() 38 | .filter(|file| file.name.contains("gitignore")) 39 | .collect(); 40 | 41 | // destructure vec of structs to vec of tuples in form (name, url) 42 | let destructured: Vec<(String, String)> = gitignore_files 43 | .iter() 44 | .map(|file| destructure_to_tup(file)) 45 | .collect(); 46 | 47 | // collect vector of tuples into a hashmap 48 | let file_map: HashMap = destructured.into_iter().collect(); 49 | 50 | return file_map; 51 | } 52 | 53 | // destructure FileRes struct to a tuple of its fields 54 | pub fn destructure_to_tup(file_struct: &FileRes) -> (String, String) { 55 | // format name to be language name lowercased 56 | let name: String = file_struct 57 | .name 58 | .clone() 59 | .replace(".gitignore", "") 60 | .to_lowercase(); 61 | 62 | let mut url: String = String::from(""); 63 | 64 | if let Some(download_url) = &file_struct.download_url { 65 | url.push_str(download_url); 66 | } 67 | 68 | return (name, url); 69 | } 70 | 71 | // make http get request for the specified template and return the raw text of the gitignore as a string 72 | pub fn get_raw_ignore_file(file_map: &HashMap, lang: &str) -> String { 73 | let mut response: String = String::from(""); 74 | let file_url: Option<&String> = file_map.get(lang); 75 | 76 | if let Some(file) = file_url { 77 | response.push_str(&http_get(&file)); 78 | } 79 | 80 | return response; 81 | } 82 | 83 | // Coalesces consecutives globstars into a single globstar 84 | pub fn reduce_globstars(path: &str) -> String { 85 | let path_parts = path.split("/"); 86 | let coalesced_parts = path_parts.coalesce(|x, y| { 87 | if x == GLOBSTAR && y == GLOBSTAR { 88 | Ok(GLOBSTAR) 89 | } else { 90 | Err((x, y)) 91 | } 92 | }); 93 | 94 | return coalesced_parts.collect::>().join("/"); 95 | } 96 | 97 | // Add title for each raw gitignore and add prefix paths to all non-comment lines 98 | fn format_gitignore(raw_body: &String, prefix_path: Option<&Path>, language: &str) -> String { 99 | let mut body = String::with_capacity(raw_body.len() * 2); 100 | 101 | if let Some(path) = prefix_path { 102 | for line in raw_body.lines() { 103 | // Check if the line is a comment or empty by consuming all the whitespace and then checking 104 | // if the next character is a hash 105 | let first_non_whitespace_char = 106 | line.chars().skip_while(|c| c.is_ascii_whitespace()).next(); 107 | 108 | if first_non_whitespace_char == Some('#') || first_non_whitespace_char == None { 109 | // If the line is a comment or blank then add it to the file untouched 110 | body.push_str(line); 111 | } else { 112 | // Trim the '!' off the input line (if it exists) and add it to the start of the 113 | // output line 114 | let trimmed_line = if first_non_whitespace_char == Some('!') { 115 | // The line is an exclusion, so remove the '!' from the start of the path and 116 | // add it to the output string 117 | body.push('!'); 118 | &line[1..] 119 | } else { 120 | line 121 | }; 122 | 123 | // A lot of gitignores seem to have erroneous '/'s at the start of their paths, but 124 | // rust is not magic so it can't figure out which ones are actually correct so this 125 | // will just remove them all 126 | let corrected_line = if trimmed_line.chars().next() == Some('/') { 127 | trimmed_line.chars().skip(1).collect::() 128 | } else { 129 | trimmed_line.to_string() 130 | }; 131 | 132 | let prefixed_path = path.join(Path::new(&corrected_line)); 133 | let prefixed_path_str = prefixed_path.to_str().expect("Path is not valid unicode"); 134 | let final_path_str = &reduce_globstars(prefixed_path_str); 135 | let final_path = Path::new(final_path_str); 136 | 137 | 138 | body.push_str( 139 | final_path 140 | .to_str() 141 | .expect("Bad path found in gitignore."), 142 | ); 143 | } 144 | 145 | body.push('\n'); 146 | } 147 | 148 | // Remove the newline at the end of the body 149 | assert_eq!(body.pop(), Some('\n')); 150 | } else { 151 | // The prefix path is None, so we can just copy the body as-is 152 | body.push_str(raw_body); 153 | } 154 | 155 | let ignore_template: String = format!( 156 | "# {} gitignore generated by Blindfold\n\n{}\n\n", 157 | language.to_uppercase(), 158 | body 159 | ); 160 | 161 | println!("Generated .gitignore for {} 🔧", language.magenta().bold()); 162 | return ignore_template; 163 | } 164 | 165 | // returns formatted gitignore string for each language provided 166 | pub fn generate_gitignore_file(languages: Vec<&str>, file_map: &HashMap) -> String { 167 | // string to store all the gitignores 168 | let mut gitignore: String = String::from(""); 169 | 170 | // generate gitignore for each language and append to output string 171 | for path_and_language in languages.iter() { 172 | // Split the path and language, with path being None if the language name doesn't contain a 173 | // '/' 174 | let last_slash_index = path_and_language.rfind('/'); 175 | let language = &path_and_language[last_slash_index.map_or(0, |x| x + 1)..]; 176 | let prefix_path = last_slash_index.map(|i| Path::new(&path_and_language[..i + 1])); 177 | 178 | // make sure a language is added 179 | if language == "" { 180 | continue; 181 | } 182 | 183 | if file_map.contains_key(&language.to_string()) { 184 | let ignore_body: String = get_raw_ignore_file(&file_map, language); 185 | gitignore.push_str(&format_gitignore(&ignore_body, prefix_path, language)); 186 | } else { 187 | let stdio = stdin(); 188 | let input = stdio.lock(); 189 | let output = stdout(); 190 | 191 | let most_similar: Option = 192 | suggest_most_similar(input, output, language.clone(), file_map.clone()); 193 | 194 | if let Some(language) = most_similar { 195 | let ignore_body: String = get_raw_ignore_file(&file_map, &language); 196 | gitignore.push_str(&format_gitignore(&ignore_body, prefix_path, &language)); 197 | } 198 | } 199 | } 200 | 201 | return gitignore; 202 | } 203 | 204 | // given a mis-typed language this function returns the most similar language available 205 | pub fn suggest_most_similar( 206 | mut reader: R, 207 | mut writer: W, 208 | typo: &str, 209 | file_map: HashMap, 210 | ) -> Option 211 | where 212 | R: BufRead, 213 | W: Write, 214 | { 215 | // find language most similar to what was requested 216 | let mut max: f64 = 0.0; 217 | let mut most_similar: String = String::new(); 218 | 219 | for candidate in file_map.keys() { 220 | let similarity: f64 = normalized_levenshtein(typo, candidate); 221 | if similarity > max { 222 | most_similar = candidate.to_string(); 223 | max = similarity; 224 | } 225 | } 226 | 227 | // take input to accept/deny suggestion 228 | write!( 229 | &mut writer, 230 | "Couldn't generate template for {}, did you mean {}? [y/N]: ", 231 | typo.yellow().bold(), 232 | most_similar.bright_green().bold() 233 | ) 234 | .expect("Unable to write"); 235 | // flush input buffer so that it prints immediately 236 | stdout().flush().ok(); 237 | 238 | let mut choice: String = String::new(); 239 | reader 240 | .read_line(&mut choice) 241 | .ok() 242 | .expect("Couldn't read line"); 243 | 244 | if choice.to_lowercase().trim() == String::from("y") { 245 | return Some(most_similar); 246 | } 247 | 248 | return None; 249 | } 250 | 251 | // writes gitignore string to file 252 | pub fn write_to_file(dest: &str, gitignore: String) -> std::io::Result<()> { 253 | let filepath: PathBuf = Path::new(dest).join(".gitignore"); 254 | println!( 255 | "Writing file to {}... ✏️ ", 256 | filepath 257 | .to_str() 258 | .expect("Unknown output file name.") 259 | .bright_blue() 260 | .bold() 261 | ); 262 | let mut file = File::create(filepath)?; 263 | file.write_all(gitignore.as_bytes())?; 264 | println!("{} ✨", "Done!".green().bold()); 265 | 266 | Ok(()) 267 | } 268 | 269 | // add gitignore to existing gitignore file 270 | pub fn append_to_file(destination: &str, gitignore: String) -> std::io::Result<()> { 271 | let filepath: PathBuf = Path::new(destination).join(".gitignore"); 272 | 273 | // open existing gitignore and concatenate with new template 274 | let mut file = File::open(&filepath)?; 275 | let mut existing: String = String::new(); 276 | file.read_to_string(&mut existing)?; 277 | let combined: String = format!("{}{}", existing, gitignore); 278 | 279 | if !combined.is_empty() { 280 | println!( 281 | "Loaded existing gitignore file from {} 💾", 282 | filepath 283 | .to_str() 284 | .expect("Unknown file path.") 285 | .bright_blue() 286 | .bold() 287 | ); 288 | 289 | // write it to file 290 | write_to_file(destination, combined).expect("Couldn't write to file ⚠️ "); 291 | } 292 | 293 | return Ok(()); 294 | } 295 | 296 | // print a table containing all available templates for generation 297 | pub fn list_templates(file_map: HashMap) { 298 | let mut table = Table::new(); 299 | 300 | let mut keys: Vec = file_map.keys().map(|key| key.clone()).collect(); 301 | 302 | keys.sort(); 303 | 304 | let mut chunks = keys.chunks(4); 305 | 306 | // while another row can be constructed, construct one and add to table 307 | while let Some(chunk) = chunks.next() { 308 | // map chunk items to cell 309 | let cells = chunk.iter().map(|item| Cell::new(item)).collect(); 310 | 311 | let row = Row::new(cells); 312 | table.add_row(row); 313 | } 314 | 315 | // print table 316 | table.printstd(); 317 | } 318 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg, SubCommand}; 2 | use colored::*; 3 | use std::collections::HashMap; 4 | mod lib; 5 | #[cfg(test)] 6 | mod test; 7 | 8 | // API endpoint for the gitignore templates repository 9 | const API_URL: &str = "https://api.github.com/repos/toptal/gitignore/contents/templates?ref=master"; 10 | 11 | fn main() -> std::io::Result<()> { 12 | let matches = App::new("Blindfold") 13 | .version("1.0") 14 | .author("Eoin McMahon ") 15 | .about("Grabs gitignore templates from gitignore.io") 16 | .arg(Arg::with_name("LANGUAGE(S)") 17 | .short("l") 18 | .long("lang") 19 | .takes_value(true) 20 | .multiple(true) 21 | .help("Template(s) to generate gitignore for i.e Rust, Flutter, VsCode etc. WARNING: this will override any current gitignore")) 22 | .arg(Arg::with_name("APPEND LANGUAGE(S)") 23 | .short("a") 24 | .long("append") 25 | .takes_value(true) 26 | .multiple(true) 27 | .help("Adds template(s) to pre-existing gitignore file_map")) 28 | .arg(Arg::with_name("DESTINATION") 29 | .short("d") 30 | .long("dest") 31 | .help("Destination to store the gitignore file in") 32 | .takes_value(true)) 33 | .subcommand(SubCommand::with_name("list") 34 | .about("Lists all available gitignore templates")) 35 | .get_matches(); 36 | 37 | // perform a get request to list the gitignore repository files 38 | let repo_contents: String = lib::http_get(API_URL); 39 | let file_map: HashMap = lib::build_file_map(&repo_contents); 40 | 41 | // unwrap arguments and generate gitignore 42 | let destination: &str = matches.value_of("DESTINATION").unwrap_or("./"); 43 | 44 | // if passed list command, list and return 45 | if let Some(_) = matches.subcommand_matches("list") { 46 | lib::list_templates(file_map); 47 | return Ok(()); 48 | } else if matches.is_present("LANGUAGE(S)") { 49 | let languages: Vec<&str> = matches.values_of("LANGUAGE(S)").unwrap().collect(); 50 | let gitignore: String = lib::generate_gitignore_file(languages, &file_map); 51 | // write gitignore to file 52 | 53 | if !gitignore.is_empty() { 54 | lib::write_to_file(destination, gitignore).expect("Couldn't write to file ⚠️ "); 55 | return Ok(()); 56 | } 57 | } else if matches.is_present("APPEND LANGUAGE(S)") { 58 | let additional_languages: Vec<&str> = 59 | matches.values_of("APPEND LANGUAGE(S)").unwrap().collect(); 60 | let gitignore: String = lib::generate_gitignore_file(additional_languages, &file_map); 61 | 62 | if !gitignore.is_empty() { 63 | // append to existing gitignore to file 64 | lib::append_to_file(destination, gitignore).expect("Couldn't write to file ⚠️ "); 65 | return Ok(()); 66 | } 67 | } 68 | 69 | // if no arguments are supplied, exit 70 | println!("{}, no gitignore to write! ⚠️", "Stopping".red()); 71 | 72 | return Ok(()); 73 | } 74 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use std::collections::HashMap; 4 | 5 | #[test] 6 | #[should_panic] 7 | fn http_get() { 8 | // make sure a valid url does not return nothing 9 | assert_ne!( 10 | blindfold::http_get( 11 | "https://api.github.com/repos/toptal/gitignore/\ 12 | contents/templates?ref=master" 13 | ), 14 | "" 15 | ); 16 | // make sure that an invalid url causes an error (ok because the repo url is hardcoded) 17 | panic!(blindfold::http_get("www.notarealsite/foo/bar")); 18 | } 19 | 20 | #[test] 21 | fn coalesece_globstars() { 22 | 23 | let path_reduction_map = &[ 24 | ("test/test", "test/test"), 25 | ("test/**/test", "test/**/test"), 26 | ("test/**/**/test", "test/**/test"), 27 | ("test/**/**/**/test", "test/**/test"), 28 | ("test/**/test/**/test", "test/**/test/**/test") 29 | ]; 30 | 31 | 32 | for (path, correct_reduction) in path_reduction_map { 33 | let reduction = crate::lib::reduce_globstars(path); 34 | assert_eq!(&reduction, correct_reduction); 35 | } 36 | } 37 | 38 | #[test] 39 | fn generate_gitignore_file() { 40 | // setup 41 | let mut map: HashMap = HashMap::new(); 42 | map.insert( 43 | String::from("rust"), 44 | String::from( 45 | "https://raw.githubusercontent.com/toptal/gitignore/\ 46 | master/templates/Rust.gitignore", 47 | ), 48 | ); 49 | 50 | let langs = vec!["rust"]; 51 | let empty_lang = vec![""]; 52 | let has_empty_lang = vec!["", "rust"]; 53 | 54 | // add a single language and generate a gitignore for it 55 | assert_eq!(blindfold::generate_gitignore_file(langs, &map), 56 | "# RUST gitignore generated by Blindfold\n\n# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n\n"); 57 | // empty vector should return an empty string 58 | assert_eq!(blindfold::generate_gitignore_file(empty_lang, &map), ""); 59 | // if there is an empty language, it should return a gitignore for the valid languages 60 | assert_eq!(blindfold::generate_gitignore_file(has_empty_lang, &map), 61 | "# RUST gitignore generated by Blindfold\n\n# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n\n"); 62 | } 63 | 64 | #[test] 65 | fn get_gitignore_file() { 66 | // setup 67 | let mut map: HashMap = HashMap::new(); 68 | map.insert( 69 | String::from("rust"), 70 | String::from( 71 | "https://raw.githubusercontent.com/toptal/gitignore/\ 72 | master/templates/Rust.gitignore", 73 | ), 74 | ); 75 | let language = "rust"; 76 | 77 | // get raw gitignore from the github api 78 | assert_eq!(blindfold::get_raw_ignore_file(&map, language), 79 | "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n"); 80 | // non existent language should return an empty string 81 | assert_eq!(blindfold::get_raw_ignore_file(&map, ""), ""); 82 | } 83 | 84 | #[test] 85 | fn suggest_most_similar() { 86 | // setup 87 | let mut map: HashMap = HashMap::new(); 88 | map.insert(String::from("rust"), String::from("rust gitignore url")); 89 | map.insert(String::from("c++"), String::from("c++ gitignore url")); 90 | let language = "roost"; 91 | let dissimilar_language = "this is not a language, but should still give a suggestion"; 92 | 93 | // function takes a y/n from standard input, so need to pass this as input to the function 94 | let yes_input = b"y"; 95 | let no_input = b"n"; 96 | 97 | // not testing std out so can just pass empty vec 98 | let mut output = Vec::new(); 99 | let yes_answer = 100 | blindfold::suggest_most_similar(&yes_input[..], &mut output, &language, map.clone()); 101 | 102 | let no_answer = 103 | blindfold::suggest_most_similar(&no_input[..], &mut output, &language, map.clone()); 104 | 105 | let yes_answer_dissimilar = blindfold::suggest_most_similar( 106 | &yes_input[..], 107 | &mut output, 108 | &dissimilar_language, 109 | map.clone(), 110 | ); 111 | 112 | // most similar should be rust 113 | assert_eq!(yes_answer, Some(String::from("rust"))); 114 | // if not accepted the most similar should be `None` 115 | assert_eq!(no_answer, None); 116 | // even if the typo is very different, it should still give a suggestion 117 | assert_ne!(yes_answer_dissimilar, None); 118 | } 119 | } 120 | --------------------------------------------------------------------------------