1 use std::collections::BTreeSet; 2 use std::fs; 3 use std::io::Read; 4 use std::path::PathBuf; 5 6 use console::style; 7 use failure::Error; 8 use ignore::overrides::OverrideBuilder; 9 use ignore::types::TypesBuilder; 10 use ignore::WalkBuilder; 11 use log::info; 12 13 use crate::utils::progress::{ProgressBar, ProgressStyle}; 14 15 pub struct ReleaseFileSearch { 16 path: PathBuf, 17 extensions: BTreeSet<String>, 18 ignores: BTreeSet<String>, 19 ignore_file: Option<String>, 20 } 21 22 #[derive(Eq, PartialEq, Hash)] 23 pub struct ReleaseFileMatch { 24 pub base_path: PathBuf, 25 pub path: PathBuf, 26 pub contents: Vec<u8>, 27 } 28 29 impl ReleaseFileSearch { new(path: PathBuf) -> Self30 pub fn new(path: PathBuf) -> Self { 31 ReleaseFileSearch { 32 path, 33 extensions: BTreeSet::new(), 34 ignore_file: None, 35 ignores: BTreeSet::new(), 36 } 37 } 38 extension<E>(&mut self, extension: E) -> &mut Self where E: Into<String>,39 pub fn extension<E>(&mut self, extension: E) -> &mut Self 40 where 41 E: Into<String>, 42 { 43 self.extensions.insert(extension.into()); 44 self 45 } 46 extensions<E>(&mut self, extensions: E) -> &mut Self where E: IntoIterator, E::Item: Into<String>,47 pub fn extensions<E>(&mut self, extensions: E) -> &mut Self 48 where 49 E: IntoIterator, 50 E::Item: Into<String>, 51 { 52 for extension in extensions { 53 self.extensions.insert(extension.into()); 54 } 55 self 56 } 57 ignore<I>(&mut self, ignore: I) -> &mut Self where I: Into<String>,58 pub fn ignore<I>(&mut self, ignore: I) -> &mut Self 59 where 60 I: Into<String>, 61 { 62 self.ignores.insert(ignore.into()); 63 self 64 } 65 ignores<I>(&mut self, ignores: I) -> &mut Self where I: IntoIterator, I::Item: Into<String>,66 pub fn ignores<I>(&mut self, ignores: I) -> &mut Self 67 where 68 I: IntoIterator, 69 I::Item: Into<String>, 70 { 71 for ignore in ignores { 72 self.ignores.insert(ignore.into()); 73 } 74 self 75 } 76 ignore_file<P>(&mut self, path: P) -> &mut Self where P: Into<String>,77 pub fn ignore_file<P>(&mut self, path: P) -> &mut Self 78 where 79 P: Into<String>, 80 { 81 let path = path.into(); 82 if !path.is_empty() { 83 self.ignore_file = Some(path); 84 } 85 self 86 } 87 collect_file(path: PathBuf) -> Result<ReleaseFileMatch, Error>88 pub fn collect_file(path: PathBuf) -> Result<ReleaseFileMatch, Error> { 89 let mut f = fs::File::open(path.clone())?; 90 let mut contents = Vec::new(); 91 f.read_to_end(&mut contents)?; 92 Ok(ReleaseFileMatch { 93 base_path: path.clone(), 94 path, 95 contents, 96 }) 97 } 98 collect_files(&self) -> Result<Vec<ReleaseFileMatch>, Error>99 pub fn collect_files(&self) -> Result<Vec<ReleaseFileMatch>, Error> { 100 let progress_style = ProgressStyle::default_spinner().template( 101 "{spinner} Searching for release files...\ 102 \n found {prefix:.yellow} {msg:.dim}", 103 ); 104 105 let progress = ProgressBar::new_spinner(); 106 progress.enable_steady_tick(100); 107 progress.set_style(progress_style); 108 109 let mut collected = Vec::new(); 110 111 let mut builder = WalkBuilder::new(&self.path); 112 builder 113 .follow_links(true) 114 .git_exclude(false) 115 .git_ignore(false) 116 .ignore(false); 117 118 if !&self.extensions.is_empty() { 119 let mut types_builder = TypesBuilder::new(); 120 for ext in &self.extensions { 121 let ext_name = ext.replace('.', "__"); 122 types_builder.add(&ext_name, &format!("*.{}", ext))?; 123 } 124 builder.types(types_builder.select("all").build()?); 125 } 126 127 if let Some(ignore_file) = &self.ignore_file { 128 // This could yield an optional partial error 129 // We ignore this error to match behavior of git 130 builder.add_ignore(ignore_file); 131 } 132 133 if !&self.ignores.is_empty() { 134 let mut override_builder = OverrideBuilder::new(&self.path); 135 for ignore in &self.ignores { 136 override_builder.add(ignore)?; 137 } 138 builder.overrides(override_builder.build()?); 139 } 140 141 for result in builder.build() { 142 let file = result?; 143 if file.file_type().map_or(false, |t| t.is_dir()) { 144 continue; 145 } 146 progress.set_message(&format!("{}", file.path().display())); 147 148 info!( 149 "found: {} ({} bytes)", 150 file.path().display(), 151 file.metadata().unwrap().len() 152 ); 153 154 let mut f = fs::File::open(file.path())?; 155 let mut contents = Vec::new(); 156 f.read_to_end(&mut contents)?; 157 158 let file_match = ReleaseFileMatch { 159 base_path: self.path.clone(), 160 path: file.path().to_path_buf(), 161 contents, 162 }; 163 collected.push(file_match); 164 165 progress.set_prefix(&collected.len().to_string()); 166 } 167 168 progress.finish_and_clear(); 169 println!( 170 "{} Found {} release {}", 171 style(">").dim(), 172 style(collected.len()).yellow(), 173 match collected.len() { 174 1 => "file", 175 _ => "files", 176 } 177 ); 178 179 Ok(collected) 180 } 181 } 182